конфиг: одна версия и один служебный файл, .av-dev.toml в корне

Версий было две — канон 14 в docs/.docs.json и формат задач 1 в
<каталог задач>/.tasks.json, — и порознь они двигались потому, что плагины
ставились порознь. Плагин один, версия одна и начинается с 1; журналы обеих
прежних нумераций закрыты и лежат рядом непереписанными, действующий журнал
открывается записью о слиянии с перечнем шагов проекту.

Формат TOML взят ради комментариев: файл живёт в репозитории проекта, и
назначение числа читают из него самого. Отсюда правило записи — скрипты правят
строку, а не переписывают файл. Читатель общий, shared/config.py: два разбора
одной схемы были бы двумя домами.

Каталог задач перестал узнаваться служебным файлом и называется ключом
[tasks] dir; узнают его по индексу. Прежние файлы не читаются — увидев их,
docs.py и tasks.py называют прежнюю раскладку и зовут upgrade.
This commit is contained in:
av
2026-08-13 10:30:18 +03:00
parent 6b162c421d
commit 95c9499f06
16 changed files with 1450 additions and 1117 deletions
+18 -16
View File
@@ -27,7 +27,8 @@ description: Привести проект к канону документов
записок разведки, и дом у них общий — `shared/language.md` в репозитории
плагинов, а этот файл его копия. Вычитывают их два прохода по охвату:
документы — `doc-wording`, записи каталога задач — `task-wording`.
- [references/changelog.md](references/changelog.md) — журнал версий канона.
- [references/changelog.md](references/changelog.md) — журнал версий раскладки;
закрытые журналы до слияния плагинов лежат рядом.
## Три правила, из которых всё следует
@@ -184,7 +185,8 @@ capability), `openspec/config.yaml`.
Порядок важен — он минимизирует окно, в котором ссылки битые:
1. `docs/.docs.json` с `{"canon": <текущая версия>}` и путём миграций, если БД есть;
1. `.av-dev.toml` в корне: `version = <текущая версия>` и путь миграций в
`[docs]`, если БД есть;
2. каталоги канона и скелет **по [references/skeletons.md](references/skeletons.md)**:
незаполненное — одной честной информативной строкой, а не «TBD»;
3. **OpenSpec, если его нет или `config.yaml` остался примером** — **вызови
@@ -209,10 +211,10 @@ capability), `openspec/config.yaml`.
**Шагов в гейте три, и они независимы.** `docs.py check` не тянет за собой
ни задачи, ни конвейер: без своих строк дрейф каталога задач и формы
`openspec/config.yaml` перестаёт ловиться совсем. Ставь соседские шаги по
следу присутствия — `<каталог задач>/.tasks.json` есть, значит ставится
`openspec/config.yaml` перестаёт ловиться совсем. Ставь соседние шаги по
следу присутствия — каталог задач с индексом на месте, значит ставится
`tasks.py check --dir <каталог задач>`; `openspec/config.yaml` есть, значит
ставится `openspec.py check`. Следа нет — плагина в проекте нет, шаг не
ставится `openspec.py check`. Следа нет — этой части в проекте нет, шаг не
ставится, и это **строка доклада**, а не поломка: назови, чего теперь не
проверяет никто. У каждого шага своя переменная пути с тем же умолчанием на
канонический путь маркетплейса; `$CLAUDE_PLUGIN_ROOT` в гейт не подставляй —
@@ -273,7 +275,8 @@ capability), `openspec/config.yaml`.
3. Иначе иди по [changelog.md](references/changelog.md) снизу вверх от версии
проекта до текущей и делай названное в каждой записи. Записи независимы и
применяются по порядку.
4. Подними `canon` в `docs/.docs.json` до текущей.
4. Подними `version` в `.av-dev.toml` до текущей — правь **строку**, а не
переписывай файл: комментарии в нём принадлежат проекту.
5. `docs.py check`.
6. **Позови судей** — Skill `av-dev:doc-healthcheck`.
7. **Позови вычитку** — агент `doc-wording`, но **только по тем документам,
@@ -284,17 +287,16 @@ capability), `openspec/config.yaml`.
Записи журнала описывают **что сделать проекту**. Если запись этого не говорит —
это дефект журнала, и о нём надо сказать, а не догадываться.
**Каталог задач повышается своим журналом, а не этим.** У него своя версия
формата — ключ `tasks` в `<каталог задач>/.tasks.json`, — и двигает её плагин
`av-dev-tasks`. Запись канона вправе сказать «позови соседа», но не вправе
двигать чужое число: две версии, которые ходят по одному журналу, разъезжаются
на первом же проекте, поставившем один плагин без другого. Отстал каталог
задач — это скажет `tasks.py check` своей строкой гейта, а повысит скилл
`av-dev:task-track`.
**Каталог задач повышается этим же журналом.** Версия одна на всю раскладку —
`version` в `.av-dev.toml`, — и записи журнала говорят про обе половины: и про
документы, и про каталог задач. Порознь версии жили, пока плагинов было три и
проект мог взять одну половину без другой; с одним плагином два числа означали
бы только вопрос, по какому журналу повышать. Что каталог задач отстал, скажет
`tasks.py check` своей строкой гейта — той же версией, что и `docs.py`.
**Шаг 6 обязателен, и вот почему.** `check` сверяет **число** в `.docs.json` с
версией скрипта — и только его. Применена ли запись журнала **по существу**, он
не знает: проект несёт `"canon": 6` и может не иметь того, чего требовала любая
**Шаг 6 обязателен, и вот почему.** `check` сверяет **число** в `.av-dev.toml`
с версией скрипта — и только его. Применена ли запись журнала **по существу**,
он не знает: проект несёт `version = 6` и может не иметь того, чего требовала любая
из пройденных версий. Записи применяются руками (переименовать секцию, проставить
типы, дописать раздел каждому `fix`), а ручной проход по нескольким записям
подряд — ровно то место, где половина шага делается и забывается. Судьи и есть
+36 -33
View File
@@ -45,8 +45,9 @@
CLAUDE.md памятка агенту: что это, стек, инварианты с
severity, команды, семантика гейта, запреты
AGENTS.md необязателен, лежит рядом; читается теми же
.av-dev.toml версия раскладки и настройки проверок; лежит
в корне, потому что нужен и без docs/
docs/
.docs.json версия канона и пути, нужные проверкам
passport.md | passport/ зачем и для кого; чем НЕ является; сценарии
architecture.md | architecture/ как сложено — обзор; окружение и эксплуатация
database.md | database/ схема хранилища; представление данных и настройки
@@ -100,7 +101,7 @@ openspec/
| `review.*` | процессный | — (настройка самого конвейера, слой **над** темами) |
| `adr.*` | процессный | — |
| `research.*` | процессный | — |
| `.docs.json` | процессный | — (служебный файл, не документ) |
| `.av-dev.toml` | процессный | — (служебный файл, не документ) |
**Список тем открытый, и это не послабление, а механизм.** Категории
`источник` и `процессный` **закрыты** — они перечислены здесь поимённо и
@@ -343,10 +344,10 @@ kebab-case.** Причина не эстетическая: имя файла с
### `tasks/`
**Каталог задач канону не принадлежит.** Его ведёт отдельный плагин
`av-dev-tasks` — своим скриптом, своим конфигом `<каталог>/.tasks.json`, своей
версией формата в нём же и своим журналом версий. Канон о том числе не
высказывается и его не двигает: повышает каталог задач тот, кто его ведёт.
**Каталог задач канону не принадлежит.** Его ведёт скилл `task-track` — своим
скриптом и своими проверками; где каталог лежит и как названы его части, говорит
секция `[tasks]` в `.av-dev.toml`. Версия там одна на всю раскладку: канон и
каталог задач двигаются вместе, потому что двигает их один плагин.
Канон **резервирует место** в `docs/` и внутрь не смотрит:
`docs.py` каталог не открывает, его отсутствия не считает дрейфом и согласованность
задач не проверяет. Проект, поставивший только канон документов, задач не ведёт
@@ -538,39 +539,41 @@ kebab-case.** Причина не эстетическая: имя файла с
архитектуру с кодом» задача без дна, и агент, которому её поставили, выдаёт
правдоподобную труху вместо находок.
## `docs/.docs.json`
## `.av-dev.toml`
```json
{
"canon": <текущая версия>,
"migrations": "internal/store/migrations"
}
```toml
# Раскладка av-dev в этом проекте: версия и настройки проверок.
version = 1 # версия раскладки
[docs]
migrations = "internal/store/migrations" # если БД есть
[tasks]
dir = "tasks" # каталог задач от корня репозитория
```
`canon` — версия канона, под которую проект приведён, целым числом: обратной
совместимости у канона нет, есть «приведён» и «не приведён». Число подставляет
`version` — версия раскладки, под которую проект приведён, целым числом:
обратной совместимости нет, есть «приведён» и «не приведён». Число подставляет
`init`, `adopt` или `upgrade`, и берётся оно из `docs.py version`, а не из
образца: литерал в образце протухает на первом же повышении канона.
`migrations` — путь каталога миграций, если БД есть; по нему `docs.py` делает
сверку с `database.md`.
образца: литерал в образце протухает на первом же повышении.
`[docs] migrations` — путь каталога миграций, если БД есть; по нему `docs.py`
делает сверку с `database.md`. `[tasks]` — где лежит каталог задач и как названы
его части; состав ключей описывает скилл `task-track`.
**Имя файла — имя плагина, который его завёл.** Канон документов ведёт
`av-dev-docs`, поэтому `.docs.json`; у каталога задач по тому же правилу
`.tasks.json`, у конвейера — `openspec/config.yaml`. До версии 13 файл звался
`.pm.json` — по плагину `av-dev-pm`, который распался на четыре и которого
больше нет; имя пережило владельца и указывало в пустоту. Прежнее имя `docs.py`
не читает: два дома для одной версии канона расходятся молча, а переименование
стоит одну команду (версия 13 журнала, и `check` называет её сам, когда видит
старый файл).
**Формат TOML взят ради комментариев.** Файл лежит в репозитории проекта, и
человек, открывший его через полгода, обязан прочитать в нём, что означает
число. JSON комментариев не знает, и объяснение приходилось держать в другом
файле. Отсюда же правило записи: скрипты правят **строку**, а не переписывают
файл — перезапись стёрла бы то, ради чего формат и выбран.
**Ключа `tasks` здесь больше нет.** Настройки каталога задач вернулись в свой
файл `<каталог задач>/.tasks.json`, потому что ведёт их другой плагин: конфиг,
лежащий в `docs/`, был бы домом, которого нет у проекта, поставившего учёт работ
без канона документов. Состав ключей описывает тот плагин, а не канон. Там же —
**версия формата задач**, и она своя: у проекта без `docs/` версии канона нет
вовсе, сверять её было бы не с чем. Прежний ключ читается, пока живы
непереехавшие проекты, и `tasks.py` говорит о нём замечанием на каждом
прогоне — версия 8 журнала просит его убрать.
**Файл один, и лежит он в корне.** До слияния плагинов их было два —
`docs/.docs.json` с версией канона и `<каталог задач>/.tasks.json` с версией
формата задач, — и версии двигались порознь, потому что плагины ставились
порознь. Плагин теперь один, версия одна, а корень выбран потому, что он есть и
у проекта без `docs/`, и у проекта без каталога задач. Прежние имена не
читаются: два дома для одной версии расходятся молча. Увидев их, `docs.py` и
`tasks.py` говорят «прежняя раскладка» и зовут `upgrade` — версия 1 журнала.
Ключей будет больше по мере роста проверок; неизвестный ключ `docs.py`
игнорирует, отсутствующий — считает «проверка неприменима» и говорит об этом
@@ -0,0 +1,784 @@
# Журнал версий канона до слияния плагинов
**Журнал закрыт.** Он описывает версии канона документов 1–14 — время, когда
плагинов было три и у канона была своя нумерация. Действующий журнал —
[changelog.md](changelog.md), и его версия 1 идёт **после** записи 14 отсюда.
Записи не переписаны под нынешние имена: адрес и имя скилла, верные на день
записи, остаются там как свидетельство. Записи ниже версии 13 зовут служебный
файл `docs/.pm.json` — так и было; переименование делает запись 13, а переезд в
`.av-dev.toml` — запись 1 действующего журнала.
Проект, отставший от канона 14, идёт сперва по этим записям снизу вверх от своей
версии до 14, и только потом переходит в действующий журнал.
---
## Версия 14 — 2026-08-11
У ADR стало два законных источника. Прежде запись цитировала только архивный
`design.md`, то есть решение, принятое по ходу изменения. Решение, принятое
**разведкой** — намеренный отказ, выбор подхода, «проверили и не делаем», — не
имеет `design.md` по построению: change по нему не заводится никогда. Триггер
канона такое решение ловит («намеренный отказ от очевидного подхода»), а дома у
него не было, и оно оседало в записке разведки или в переписке.
**Что изменилось.** `adr/` принимает второй источник — записку разведки. Правило
«промоут, а не второе сочинение» не тронуто: запись по-прежнему цитирует уже
написанное и **называет источник**, изменилось только то, что источников два.
Следом сказали то же самое: карта домов, разрез проверки `doc-consistency`, вход
и устав самого агента, скелеты `docs/adr/README.md` и `docs/adr/template.md`.
**Почему это версия, а не правка текста.** Два следствия уезжают в репозиторий
проекта. По карте домов судит агент согласованности — прежняя редакция читала ADR
со ссылкой на записку разведки как нарушение; а скелеты `adr/` лежат в проекте
файлами и говорят там от имени канона.
**Что сделать проекту.**
1. Ничего с существующими записями: прежние ADR ссылаются на `design.md`, и это
по-прежнему верно.
2. **Поднять шапку `docs/adr/README.md`**: «промоут поверх архивного `design.md`»
→ «промоут поверх уже написанного», с обоими источниками. Точный текст — в
[skeletons.md](skeletons.md), раздел `docs/adr/README.md`.
3. **Поднять `docs/adr/template.md`**: строка `- **Источник:**` называет два
возможных источника.
4. `docs/.docs.json`: `"canon": 14`.
**Чего делать не надо.** Заводить ADR задним числом по старым разведкам. Запись
заводится, когда решение принимается, а не когда о нём вспомнили: сочинённое
через полгода обоснование — ровно то «второе сочинение», против которого правило
и написано.
---
## Версия 13 — 2026-08-11
Служебный файл канона переименован: `docs/.pm.json``docs/.docs.json`. Имя
досталось от плагина `av-dev-pm`, который распался на четыре и которого больше
нет: файл пережил владельца и указывал в пустоту. Правило простое и теперь
соблюдается всеми тремя: **имя служебного файла — имя плагина, который его
завёл**, `.docs.json` — канон, `.tasks.json` — задачи, `openspec/config.yaml`
конвейер.
**Что изменилось.** `docs.py` читает только новое имя. Прежнее он не читает
намеренно: два дома для одной версии канона расходятся молча, а тут расхождение
стоило бы дорого — по этому числу `upgrade` решает, какие записи применять.
Файл под старым именем `check` узнаёт и называет отдельной строкой с готовой
командой, а не жалуется на пропажу.
**Что появилось у соседа.** У каталога задач теперь есть **своя версия
формата** — ключ `tasks` в `<каталог задач>/.tasks.json`, — и свой журнал версий
в скилле `av-dev-tasks:tasks`. До сих пор её не было вовсе: формат задач менялся
записями этого журнала (8, 11, 12), хотя каталог принадлежит другому плагину и
ставится без канона документов. Канон это число не двигает.
**Что сделать проекту.**
1. `git mv docs/.pm.json docs/.docs.json` — одним коммитом с шагом 2. Содержимое
не меняется: ключи те же.
2. **Поправить упоминания прежнего имени** в своих файлах — `CLAUDE.md`, гейт,
`README.md`, `docs/**`. Битой ссылкой это чаще всего не выглядит (файл
служебный, на него ссылаются прозой), поэтому `docs.py check` таких упоминаний
не ловит: ищи `grep -rn '\.pm\.json'` по репозиторию.
3. **Объявить версию формата задач**, если каталог задач в проекте есть:
`<каталог задач>/.tasks.json` с ключом `"tasks": <версия>`. Файла нет вовсе —
заведи, он теперь обязателен: версия не настройка, от которой можно
отказаться. Какое число ставить и что сделать перед этим, говорит журнал
владельца — **позови скилл `av-dev-tasks:tasks`**, здесь этих шагов нет
намеренно: второй перечень чужих шагов разошёлся бы с первым.
4. Гейт не меняется: шаги те же, версию задач сторожит `tasks.py check`, который
в нём уже стоит.
5. `docs/.docs.json`: `"canon": 13`.
**Чего делать не надо.** Ключи в файле не трогаются, документы не переезжают,
записи задач не меняются: версия 13 — про имена служебных файлов и про то, кто
чью версию двигает.
---
## Версия 12 — 2026-08-09
Спринты отменены. Работа идёт задача за задачей, и замороженный набор перестал
что-либо удерживать: он отвечал на вопрос «что делать дальше», а между наборами
на этот вопрос не отвечал никто.
**Что изменилось.** Индексов задач два вместо трёх: `SPRINT.md` упразднён.
Приоритет стал тем, чем он и является, — **порядком строк в `BACKLOG.md`**:
первая строка секции это то, что делают следующим. Назначает порядок человек,
машина его не выводит; двигают его `move --after` и `move --first` с причиной.
Гейт готовности записи стоял на взятии задачи в спринт — единственном месте, где
её судили целиком. Момент нужен и без спринта: теперь это команда
`tasks.py ready <слаг>`, и зовёт её тот, кто берёт задачу в работу.
Ритуал между спринтами (`av-dev-tasks:session`) стал скиллом груминга
(`av-dev-tasks:groom`): два вопроса — что сейчас самое важное и что перестало
быть важным.
**Что сделать проекту.**
1. **Вернуть задачи из набора в беклог и снести `SPRINT.md`.** Порядок такой:
`git rm tasks/SPRINT.md`, затем `tasks.py check --dir tasks --fix`. Строки
набора после удаления файла становятся бездомными, и `--fix` возвращает их в
беклог **в конец своей секции** — с пометкой, что позицию назначает человек.
Наоборот делать нельзя: `check` без удалённого файла увидит третий индекс и
станет ругаться на него, а не чинить.
2. **Снять теги `sprint:<слаг>`** с записей — `tasks.py edit <слаг> --rm-tag
sprint:<слаг>`. Тег больше никем не читается, а `check` о нём молчит: он
законный свободный тег. Пропущенный вреда не сделает, но и пользы не несёт.
3. **Расставить порядок** — первый груминг: `av-dev-tasks:groom`. После шага 1
очередь состоит из того, что машина поставила в конец, то есть очереди нет
вовсе. Пока порядок не назначен, «что делать дальше» по-прежнему без ответа.
4. Поправить упоминания спринта в `CLAUDE.md` проекта, если они были: слот
«общий станок» переехал в груминг под именем «что считается сломанным»,
ориентир «5–8 задач в спринте» стал ориентиром размера порции разбора.
5. `docs/.pm.json`: `"canon": 12`.
**Чего делать не надо.** `REJECTED.md`, `ROADMAP.md` и файлы `items/` не
меняются: спринт жил только в собственном индексе и в тегах.
---
## Версия 11 — 2026-08-09
Каталог задач уехал из `docs/` в корень репозитория. Версия 8 отпустила его из
канона — перестала требовать, перестала проверять, — но место он занимал всё то
же, `docs/tasks/`. Полдела: каталог, принадлежащий одному плагину, лежал внутри
дерева, которым владеет другой. Проекту, поставившему учёт работ без канона
документов, приходилось заводить `docs/` ради одной вложенной папки.
**Что изменилось.** Дом задач — `tasks/` в корне репозитория. `tasks.py` ищет его
там первым; `docs/tasks/` и `doc/tasks/` остаются в списке поиска для
непереехавших проектов, а `init` заводит только в корне. Настройки — там же,
`tasks/.tasks.json`.
**Что осталось терпимым.** `docs.py` по-прежнему не считает `docs/tasks/` файлом
вне канона: непереехавший проект не должен получать выдуманную ошибку вдобавок к
этой записи, которая и так велит ему переехать.
**Что сделать проекту.**
1. `git mv docs/tasks tasks` — одним коммитом вместе с шагом 2, чтобы ссылки не
жили битыми между коммитами.
2. **Починить относительные ссылки внутри записей.** Файл `tasks/items/x.md`
стал на уровень ближе к корню: `../../passport.md` в теле записи теперь
`../docs/passport.md`. Тот же сдвиг у ссылок из индексов. Это самая тихая
часть переезда: битая относительная ссылка не мешает `tasks.py check`, её
ловит только `docs.py check` и только у документов канона.
3. Проверить ссылки **на** задачи снаружи: `CLAUDE.md`, `README.md`, гейт,
`docs/review.md`. Путь `docs/tasks/...` в них теперь ведёт в никуда.
4. Поправить путь в гейте: `tasks.py check --dir tasks`.
5. `docs/.pm.json`: `"canon": 11`.
## Версия 10 — 2026-08-09
Проверка формы `config.yaml` ушла к тому, кто файл заводит. Версия 9 перенесла в
конвейер настройку OpenSpec и честно назвала остаток: форма и сторож версии
остались в `docs.py`, то есть у файла было два плагина — один заводит, другой
проверяет. Остаток закрыт.
**Что появилось.** Скрипт `openspec.py` в скилле `av-dev-code:openspec`, две
команды: `check --dir <корень>` — форма в проекте, `form` — сверка слепка с живым
OpenSpec. Коды выхода те же, что у `docs.py` и `tasks.py`.
**Что удалено из `docs.py`.** Константы `OPENSPEC_*`, проверка формы, сторож
версии и подкоманда `openspec-form` — 252 строки. Скрипт канона про
`openspec/config.yaml` не говорит теперь ничего; `openspec/specs/` он по-прежнему
знает, потому что это дом темы `requirements` и часть карты тем.
**Что стало лучше по дороге.** Адреса `docs/passport.md` и `CLAUDE.md` требуются
теперь **только к тем документам, которые в проекте есть**. Прежняя проверка
требовала их безусловно, то есть на проекте без канона документов требовала
битую ссылку. Теперь отсутствие документа — строка «не проверялось» с указанием,
что без канона конвейер работает вслепую.
**Что осталось за каноном.** Один вопрос, и это не форма: не пересказан ли в
`context` документ, у которого есть свой дом. Разрез — утверждение, опровергаемое
открытием другого файла, против строки «открой такой-то файл»; машине он не
виден, судит агент `doc-consistency`, и `config.yaml` у него во входе.
**Что сделать проекту.**
1. Заменить в гейте и в скриптах `docs.py openspec-form` на `openspec.py form`.
Подкоманды больше нет: прежний вызов упадёт ошибкой употребления (код 2), а не
промолчит.
2. **Добавить в гейт шаг `openspec.py check`, если проект работает по OpenSpec.**
Форму раньше проверял `docs.py check` заодно; теперь он о ней молчит, и без
отдельного шага незаменённый пример в `config.yaml` перестанет ловиться. Это
главная потеря этого повышения, и она тихая.
3. Проект по OpenSpec без установленного `av-dev-code` — форму не проверяет
никто. Либо поставить плагин, либо назвать это принятым риском вслух.
4. `docs/.pm.json`: `"canon": 10`.
## Версия 9 — 2026-08-09
OpenSpec уехал в конвейер. Каталог `openspec/` версией 7 был объявлен слотом
канона: `init` его заводил, `adopt` тоже, образец `config.yaml` лежал в скелетах,
а отсутствие каталога `docs.py` считал отказом. Разрез был проведён не там. По
OpenSpec работает конвейер — без каталога не запускаются ни `opsx:propose`, ни
ревью дизайна, ни сверка требований, — а канон документов о нём только
высказывался. Проект, которому конвейер не нужен, получал отказ за отсутствие
того, чем не пользуется.
**Что появилось.** Скилл `av-dev-code: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-code` установлен, если проект работает по
OpenSpec. Без него `docs.py check` про каталог промолчит — и молчание это
законное, так что отсутствие настройки перестанет ловиться само.
3. Проект **не** работает по OpenSpec: убедиться, что `openspec/` нет, и
перестать держать его пустым ради проверки. Она больше не требует каталога.
4. `docs/.pm.json`: `"canon": 9`.
## Версия 8 — 2026-08-09
Канон отпустил каталог задач. Плагин `av-dev-pm` расколот на `av-dev-docs`
(документы) и `av-dev-tasks` (учёт работ), и каждый теперь ставится сам по себе.
Пока владелец был один, `docs/tasks/` числился слотом канона: `docs.py` требовал
каталог, звал внутрь чужой скрипт и выдавал его дрейф за свой, а настройки задач
жили ключом `tasks` в `docs/.pm.json`. Для проекта, поставившего только документы,
всё это — отказ на ровном месте: задач он не ведёт, и требовать их не за что.
**Что изменилось.** Каталог задач канону не принадлежит; канон резервирует ему
место в `docs/` и внутрь не смотрит. `docs.py` больше не проверяет согласованность
задач вовсе — это делает `tasks.py` сам, командой своего плагина. Дом настроек
каталога задач — `<каталог задач>/.tasks.json`; ключ `tasks` в `docs/.pm.json`
читается, только пока своего файла нет, и об этом говорится замечанием.
**Что удалено.** Проверка `check_tasks` из `docs.py` и ключ `"tasks"` из скелета
`docs/.pm.json`.
**Что сделать проекту.**
1. Перенести настройки задач: содержимое ключа `"tasks"` из `docs/.pm.json` — в
`docs/tasks/.tasks.json` тем же объектом. Ключа в проекте нет (имена файлов
и заголовков умолчательные) — переносить нечего, шаг пропускается.
2. Удалить ключ `"tasks"` из `docs/.pm.json` после переноса. Оставленный он не
читается, и `tasks.py` скажет об этом замечанием на каждом прогоне.
3. Проверить, что согласованность задач по-прежнему кто-то гоняет: раньше её
тянул за собой `docs.py check`, теперь — только `tasks.py check`. **Если в
гейте проекта стоял один `docs.py`, добавить туда второй шаг** — иначе дрейф
индексов перестанет ловиться молча, и это самая вероятная потеря на этом
повышении.
4. Установить оба плагина, если нужны оба: `av-dev-docs` и `av-dev-tasks`
вместо прежнего `av-dev-pm`. Прежний из `enabledPlugins` убрать.
5. `docs/.pm.json`: `"canon": 8`.
## Версия 7 — 2026-08-07
`openspec/` был предпосылкой, о которой канон говорил, но за которой не следил.
Каталог назван в раскладке, `openspec/specs/` объявлен домом темы `requirements`,
`config.yaml` описан абзацем — а заводил всё это человек руками, и проверялось
из перечисленного ничего. Заведение нового проекта проходило мимо: `init`
собирал документы канона и оставлял проект без каталога, без которого не работают
ни `opsx:propose`, ни ревью дизайна, ни сверка требований.
Хуже отсутствия оказался файл из коробки. `openspec init` кладёт `config.yaml`,
где `context` и `rules` — закомментированный пример на английском. Такой файл
читается как настроенный: он есть, он валиден, имя правильное. Работает он как
пустой, и узнаётся это по предложению, написанному на другом языке, с
capability по имени пакета и без единого `SHALL`.
**Что изменилось:**
1. **`init` заводит OpenSpec сам** — `openspec init --tools claude`, до первого
документа канона. Команда названа в каноне поимённо, потому что её печатает
отказ `docs.py`.
2. **У `openspec/config.yaml` появилась каноническая форма** и скелет в
`skeletons.md`. Содержание — только то, что нужно **в момент порождения
артефакта**: язык, правила именования capability, придирки валидатора и
**адреса** документов канона. Пересказ паспорта, инвариантов, конвенций и
правил ревью в него не переносится.
3. **`docs.py check` проверяет пять вещей:** каталог `openspec/` есть; файл
называется `config.yaml` (`config.yml` OpenSpec читать не станет и об этом не
сообщит); `context` и `rules.specs` не остались примером, а правила для
`specs` называют `SHALL`; `context` называет `passport` и `CLAUDE.md`; ключи
под `rules:` — имена артефактов схемы, а не опечатки.
4. **За свежестью формы следит машина, а не память.** Схема и перечень
артефактов — слепок чужого инструмента; `check` сравнивает `major.minor`
установленного OpenSpec с версией, на которой форма сверялась, и при
расхождении даёт замечание. Перепроверяет `docs.py openspec-form`, и чинится
расхождение **в плагине, а не в проекте**.
5. **Шестое проверяет агент.** Отличить ссылку на документ от пересказа документа
машина не умеет — это работа `doc-consistency`, и в таблице «Что проверяет
машина, а что человек» она стоит строкой.
**Что переехало:** ничего в раскладке `docs/`. Ни один файл не переименовывается
и не перемещается.
**Что сделать проекту:**
1. Нет `openspec/` — завести: `openspec init --tools claude`. Команда кладёт ещё
и `.claude/skills/openspec-*` с `.claude/commands/opsx/*`; это её нормальная
работа, удалять их не надо.
2. Открыть `openspec/config.yaml` и привести к скелету из
[skeletons.md](skeletons.md): блок `context` с языком, правилами именования
capability, требованием `SHALL` и **адресами** `docs/passport.md` и
`CLAUDE.md`; блок `rules` с четырьмя правилами для `specs`.
3. **Вычистить из `context` пересказ.** Инварианты, перечень конвенций, состав
шагов гейта, правило выбора метки и состав проходов ревью — заменить ссылкой
на дом. Признак пересказа простой: строку можно опровергнуть, открыв другой
файл проекта.
4. Проверить имя файла: `config.yml` переименовать в `config.yaml`. Если жили оба
— содержимое `.yml` до сих пор не читалось никем, и переносить из него нужно
именно то, чего нет в `.yaml`.
5. `docs/.pm.json`: `"canon": 7`.
---
## Версия 6 — 2026-08-07
Версия 5 объявила: **каждый документ `docs/` — тема ревью**. Правило оказалось
верным ровно наполовину и потому вредным целиком. Паспорт и схему хранилища
ревью читает, но темами они не являются — они задают границу, по которой судит
чужая тема. Журнал решений и журнал наблюдений ревью изменения не нужны вовсе:
ADR объясняет прошлое решение, а не предъявляет требование к изменению.
Разметчик, применявший плоское правило буквально, обязан был либо завести
фантомные темы `passport`, `adr`, `database`, `research` и продублировать ими
работу тем `architecture` и `operations`, либо потерять четыре документа молча —
а молчащая потеря и есть то, против чего канон написан.
**Что изменилось:**
1. **Три категории документов вместо одной.** Разрез проверяемый: можно ли по
документу сказать «в этом изменении сделано не так»? **Тема** — да, прямо
(`conventions`, `security`, `architecture`, свои документы проекта).
**Источник темы** — нет, но он задаёт границу для чужой темы (`passport.*` →
`architecture`, `database.*` → `operations`, `CLAUDE.md` → `autotests`,
`openspec/specs/` → `requirements`). **Процессный документ** — нет, он про то,
как мы работаем (`tasks/`, `review.*`, `adr.*`, `research.*`, `.pm.json`).
2. **Категории `источник` и `процессный` закрыты, категория `тема` открыта.**
Прежде открытым был весь список, и «не темы ровно две» противоречило
собственной раскладке канона. Теперь пополняется только одно множество, и
документ, которого нет в раскладке, — однозначно своя тема проекта.
3. **`adr/` и `research/` уходят из входа ревью изменения.** Прогон их больше не
открывает. Проверяться они не перестали: ADR без ссылки на архивный
`design.md`, замена без парного статуса, число без провенанса — это по-прежнему
работа `doc-consistency` и `doc-code-drift`, на сессии между спринтами.
4. **`docs.py` печатает категорию в отказе.** «Нет источника passport» читается
иначе, чем «нет темы security». Обязательность при этом не изменилась:
заводятся все документы одинаково и с первого дня.
5. **У задачи появилась метка — `small`, `medium` или `large`.** Это итог
классификации и **единственный вход, по которому конвейер выбирает
исполнителей** на обеих стадиях ревью. Прежние имена `quick`, `standard` и
`wide` описывали глубину прогона, то есть свойство ревью; метка описывает
**задачу** — а выбирают по ней одно и то же. Слово «ступень» уходит:
у одной вещи одно имя.
6. **Метка выводится из двух осей и не равна ни одной из них.** Размер (малое,
среднее, крупное) и сложность (знакомое, незнакомое); метка — максимум по
ним. Малое **незнакомое** изменение получает `large`, трогая один узел, —
поэтому размер и метка пишутся отдельными строками, и выводить одно из
другого нельзя.
**Цена, записанная явно:** расхождение изменения с записанным решением прогоном
больше не ловится. Раньше архитектурный проход мог сказать «здесь отменено
решение ADR-2026-03-11, парного статуса нет»; теперь это скажет только сверка
документации. Сделка сознательная: чтение всего каталога решений оплачивалось на
каждой задаче, а срабатывало на единицах.
**Что переехало:** ничего в раскладке. Ни один файл не переименовывается и не
перемещается.
**Что сделать проекту:**
1. `docs/review.*`, подраздел «Вопросы по темам»: убрать вопросы, адресованные
`passport`, `database`, `adr`, `research` и `review` — **ни одно из этих имён
больше не тема**. Под каноном 5 темой был каждый документ `docs/`, поэтому
такие вопросы там законны и почти наверняка есть. Переадресовать:
про границу домена и про решение → `architecture`; про хранилище, настройку и
измеренное число → `operations`. Вопрос, который никуда не переадресовывается,
удалить, а не оставить висеть: адресованный несуществующей теме, он не
задаётся никем и молча.
2. Там же, «Недоступно проверке»: те же пять имён убрать из разнесения по темам,
переразнеся содержимое по оставшимся.
3. Там же: подраздел **«Триггеры профиля» → «Триггеры метки»**, и разнести его
на **три** списка вместо двух — «крупное здесь» (про объём), «незнакомое
здесь» (про форму решения) и «мелкое здесь» (опускает до `small`). Раньше
первые две оси были склеены в один список, и потому объём в правило по факту
не входил.
4. **Переименовать метки прогона везде, где проект их называет** — в «Триггерах
метки», в «Недоступно проверке», в журнале дефектов: `quick` → **`small`**,
`standard` → **`medium`**, `wide` → **`large`**. Метка это итог классификации
задачи, и три её значения — часть общего словаря канона и конвейера. Слово
«ступень» из документов уходит: у одной вещи одно имя.
5. Проверить, что свои темы проекта не совпадают именем с закрытыми категориями:
`docs/passport/`, `docs/adr/`, `docs/research/`, `docs/database/`,
`docs/review/` — это слоты канона, а не свои темы, и своим смыслом их
наполнять нельзя.
6. Ничего не заводить и не удалять: раскладка канона 6 совпадает с раскладкой
канона 5 файл в файл.
7. `docs/.pm.json`: `"canon": 6`.
---
## Версия 5 — 2026-08-06
Канон перестал быть списком файлов и стал **списком тем ревью**. Раскладка та же,
но читается иначе: документ в `docs/` — это направление проверки, а не просто
текст. Отсюда три правки, и все три развязывают то, что раньше было жёстко
сцеплено.
**Что изменилось:**
1. **Тема живёт файлом или каталогом, на выбор проекта.** `docs/security.md` и
`docs/security/` — одно и то же; тема разрослась, стала каталогом с
`README.md` — канон не сменился и версия не двинулась. Прежде форма была
задана поимённо: `conventions`, `research` и `adr` обязаны были быть
каталогами, остальные — файлами, и обосновать это было нечем. Обе формы сразу
— ошибка: два дома для одного факта расходятся молча.
2. **Список тем открытый.** Всё, что проект кладёт в `docs/`, становится темой
ревью и попадает в план каждого прогона; именной оптики у такой темы нет, её
разбирает общий проход конвейера, заведённый ровно за этим.
Прежде `docs.py` называл незнакомый файл «вне канона» — теперь называет своей
темой проекта и перечисляет их в отчёте. Не темы ровно две: `docs/tasks/` и
`docs/review.*`.
3. **`AGENTS.md` рядом с `CLAUDE.md` — законно.** Он почти стандарт; обязателен
по-прежнему только `CLAUDE.md`, но если лежат оба, читаются оба, и проверки
канона смотрят на второй так же, как на первый.
**Что переехало:**
- в `docs/review.*`: **«Вопросы к проходам» → «Вопросы по темам»**, форма
`<тема>: <вопрос> (<провенанс>)`. Причина не косметическая: вопрос,
адресованный проходу, перестал задаваться молча в тот день, когда тот уехал в
верхнюю ступень ревью. Тема переезд прохода переживает, имя прохода — нет;
- там же **«Недоступно проверке» — по темам**, оба подраздела.
**Что сделать проекту:**
1. Ничего не переименовывать, если всё уже разложено по канону 4: обе формы
дома законны, и текущая — одна из них.
2. `docs/review.*`, подраздел «Вопросы к проходам»: переименовать в «Вопросы по
темам» и переадресовать каждый вопрос теме вместо имени прохода. Темы ядра —
`requirements`, `autotests`, `conventions`, `architecture`, `security`,
`operations`.
3. Там же «Недоступно проверке»: разнести обе половины по темам.
4. Проверить, не лежит ли в `docs/` документ, который раньше считался лишним и
потому не заводился. Теперь он законен и станет темой ревью — это и есть
способ добавить проверку, которой в конвейере нет.
5. `docs/.pm.json`: `"canon": 5`.
6. Позвать судей `doc-consistency` и `doc-code-drift` — шагом 6 `upgrade`.
## Версия 4 — 2026-08-05
Две правки, обе про то, как читается каталог задач. Первая — секция роадмапа
переименована, и вместе с именем расширен её смысл; достигнутое переехало вниз.
Вторая — **у каждой записи появился тип, и тип определяет, что с записью можно
делать**. Раскладка не меняется, файлов канона не прибавляется.
**Что переехало:**
- секция роадмапа `Разработка` → **`Сопровождение`** (англ. `Tooling` →
**`Operations`**). Прежнее имя называло слишком много: роадмап **весь** про
разработку, и секция с таким именем не отличалась от остальных ничем;
- **тип записи** — из префикса заголовка (`[goal]`/`[idea]`) и тега
`kind:<род>` в **поле меты `Тип`** первой строкой. Эмодзи в заголовке от него
производна;
- **поле места** у задачи: `Секция` → **`Категория`**. У цели остаётся `Секция`:
у задачи поле называет полку домена, в которую она вернётся из спринта, у цели
— часть роадмапа, то есть состояние очереди. Одно имя на два смысла их и
смешивало.
**Что добавилось:**
1. **Смысл секции расширен.** Было «инструмент и процесс», стало «чем держат
проект: инструмент, процесс, эксплуатация». Метрики, логи, инфраструктура,
выкладка и дежурство — сюда же. Расширение не косметическое: английское
`Operations` при узком смысле обещало бы эксплуатацию, а внутри лежал бы
линтер.
2. **Общий словарь трёх мест** — [canon.md](canon.md), раздел «Сопровождение и
эксплуатация». Сопровождение — всё, чем держат проект; эксплуатация — его
часть, работа системы на проде. `ROADMAP.md`, секция `Сопровождение` — план
работ; `architecture.md`, раздел «Эксплуатация» — как устроено сейчас;
эксплуатационный проход ревью — оптика проверки. Слить их в одно слово
нельзя: они отвечают на разные вопросы. Слово **«поддержка» не употребляется
вовсе** — в нём слышится помощь пользователю.
3. **Граница с возможностями проходит по тому, кто наблюдает.** «Приложение
сообщает о своём состоянии» — возможность приложения, её место среди прочих
целей. «Дежурный видит состояние на одном экране» — сопровождение. Одни и те
же метрики попадают в разные секции роадмапа, и это верно.
4. **Порядок секций стал каноническим**, и `Готово` переехало **вниз**:
`Запланировано` | `Направления` | `Сопровождение` | `Готово`. Достигнутое
копится — через год этой секции больше, чем всех остальных вместе, — и стоя
первой она отодвигает за экран то, ради чего роадмап открывают чаще всего.
Порядок проверяет `tasks.py check`, переставляет `check --fix`.
5. **Заголовок секции отбивается пустой строкой с обеих сторон.** Прежде
проверялась только строка после заголовка; перестановка секций двигает целые
блоки, и два заголовка оказываются вплотную. Правит `check --fix`.
6. **Тип — единственная ось записи, закрытый словарь из пяти значений:**
`goal` | `feature` | `fix` | `chore` | `research`. Осей было две — тип записи
(`goal`/`idea`/`task`) и род работы (`kind:` тегом), — но из двенадцати
клеток произведения законны были шесть, а алгоритм работы крепится к роду, а
не к типу. Оси схлопнуты.
7. **Тип задаёт схему тела:** какие разделы обязательны, какие допустимы, нужна
ли цель, берётся ли запись в спринт. Проверяет `sprint take`, замечания даёт
`check`. Два раздела новые: **`Воспроизведение`** у `fix` (не
воспроизводится — это `research`, а не `fix`; правило было записано и не
проверялось) и **`Вопрос` + `Куда ляжет ответ`** у `research` вместо
критериев приёмки (приёмка разведки — записанный ответ, и критерии в форме
«оракул: тест» ей натянуты).
8. **Тип `idea` упразднён.** Он значил не род работы, а состояние
незаполненности, а состояние типом быть не может. Теперь оно называется
честно: `research` без раздела «Вопрос» — **сырьё**. В спринт не берётся, как
и прежняя идея, лежит **в конце своей категории** (проверяет `check`,
переставляет `--fix`) и отбирается `list --raw`. Порядка «по важности» в
беклоге по-прежнему нет: этот порядок производен от типа, а не назначен
человеком.
9. **Алгоритм работы над каждым типом** — отдельным файлом,
`skills/tasks/references/task-<тип>.md`: схема, что проверяет машина, что
человек, и порядок шагов.
10. **Имена файлов проверяются.** Правило «текст русский, имена английские»
стояло в каноне и не было подкреплено ничем: `docs.py` имён не смотрел вовсе.
Теперь смотрит — кириллица и не-kebab-case **жёстко**, форма имени
`ADR-ГГГГ-ММ-ДД-slug.md` жёстко, транслит **эвристикой**, то есть
замечанием. Заодно из раскладки канона убраны плейсхолдеры `<тема>.md`,
приглашавшие называть файлы по-русски.
11. **Два агента вместо обещания.** В каноне была таблица «Что проверяет машина,
а что человек», и её правая колонка три версии описывала судью, которого не
существовало. Судьи заведены и разведены по глубине: **`doc-consistency`**
(документ ↔ документ ↔ openspec: факт в двух домах, прямое противоречие,
поведение в `architecture.md` вместо спек, ADR без ссылки и парного статуса,
число без провенанса, заглушка вместо честной строки); **`doc-code-drift`**
(документ ↔ код по закрытому перечню фактов). Оба зовутся раз в спринт на
сессии, а также после adopt и после upgrade, на весь канон разом.
**Что сделать проекту:**
1. Переименовать заголовок секции в `docs/tasks/ROADMAP.md`: `## Разработка` →
`## Сопровождение` (или `## Tooling` → `## Operations`, если индекс
английский). **`check --fix` этого не сделает**: регистр канонической секции
он правит сам, а чужую секцию только называет ошибкой — смысл за человеком.
2. Поправить поле `- **Секция:**` в файлах целей, которые в ней лежат. Порядок
именно такой: сперва заголовок, потом `python3 tasks.py check --dir
docs/tasks` покажет расхождение поимённо.
3. Перечитать состав секции: цели про выкладку, метрики, логи и инфраструктуру,
если они лежали в `Направлениях` за неимением места, переезжают сюда.
4. Прогнать `python3 tasks.py check --dir docs/tasks --fix`. За один проход он
переставит секции роадмапа в канонический порядок (`Готово` уедет вниз вместе
со всем содержимым), поправит отбивку заголовков и **переведёт записи на
типы**: перенесёт значение из тега `kind:` и префикса `[goal]`/`[idea]` в поле
`Тип`, снимет тег, поставит эмодзи в заголовок, переименует `Секция` →
`Категория` у задач и снесёт сырьё в конец категорий.
5. Разобрать то, что `--fix` вернул пометкой `НЕОДНОЗНАЧНО`. Главный случай —
**записи без типа**: заведённые до появления рода работы, они не несут ни
тега, ни префикса, и машина их не угадывает (`feature` от `chore` не
отличает). Проставить руками: `edit <слаг> --type …`.
6. Дописать новые обязательные разделы у задач, которые собираются в спринт:
`Воспроизведение` у каждого `fix`, `Вопрос` и `Куда ляжет ответ` у каждого
`research`. Не «заодно по всему беклогу», а порциями переоценки: `check`
ошибкой это не считает, отказывает только `sprint take`. Сколько задач готово
к взятию, печатает блок здоровья `check`.
7. Прогнать `python3 docs.py check`: он назовёт имена файлов не по правилу.
Кириллицу и не-kebab-case править обязательно, транслит — по решению
человека. **Переименование ADR это перенос ссылок**: слаг стоит в
`adr/README.md`, в `architecture.md` и в чужих документах, и делается одним
проходом, иначе останутся битые ссылки (их `docs.py` потом и покажет).
8. `docs/review.md`, подраздел «Триггеры профиля» — переписать целиком, он
отстал дважды. Снести перечень мест для `deep`: профиль упразднён вместе с
проходом независимой реализации, и перечень стал указателем в пустоту.
Оставшийся перечень перевести на новое правило: `wide` теперь означает не
«новое понятие», а **крупное или незнакомое** изменение и рассчитан на 5–10%
задач; отдельным списком назвать, что здесь считается **мелким** (это `quick`).
Форма подраздела — в [skeletons.md](skeletons.md). Там же проверить журнал
дефектов и «Недоступно проверке» на упоминания независимой реализации: класс
«форма решения, где спека выбора не сделала» переезжает в подраздел «перестали
проверять сознательно», а рядом с ним встаёт вторая честная строка — на
`quick` и `standard` не проверяется ничего, что требует запуска.
9. `docs/.pm.json`: `"canon": 4`.
10. Позвать **обоих судей** — `doc-consistency` и `doc-code-drift`, шагом 6
`upgrade`. Пунктов выше десять, половина из них ручная, и именно здесь видно,
какие сделаны только наполовину: переименования секций и полей разводят
документы, а `check` сверяет число версии, а не существо. Первый прогон на
живом проекте вдобавок самый урожайный — правило единственного дома до сих пор
никто не проверял. Разбирать порциями, а не одним заходом.
## Версия 3 — 2026-08-04
Роадмап стал **состоянием проекта**, а не очередью работ: цель — возможность
приложения, задача — шаг к ней, достигнутое из роадмапа не исчезает. Плюс род
работы, раздел «Затрагивает» и новое умолчание профиля ревью. Раскладка меняется
в одном файле, но переименование и смена секций тянут за собой ссылки, поэтому
шаги делаются одним заходом.
**Что добавилось:**
1. **Род работы** — тег `kind:<род>` в мете задачи, словарь закрыт:
`feature` | `fix` | `chore` | `research`. Обязателен у задачи, у цели
запрещён. `sprint take` без него отказывает, `check` о пропаже напоминает
замечанием. Определение — [canon.md](canon.md), раздел `tasks/`; смысл и
причина, почему тегом, — в SKILL.md скилла `tasks`, раздел «Род работы».
2. **Раздел «Затрагивает»** в теле задачи — перечень границ, которых изменение
касается (эндпоинт, таблица и миграция, формат на диске, публичный тип). Как
и критерии приёмки, требуется к взятию в спринт, а не к заведению.
3. **Секции роадмапа** — четыре вместо двух и **канонические**, в отличие от
секций беклога: `Готово` (достигнутые цели строкой с датой, без ссылки на
файл), `Запланировано` (очередь значима), `Направления` (очереди нет),
`Разработка` (инструмент и процесс, не возможности приложения). Английский
вариант — `Done` | `Planned` | `Directions` | `Tooling`, один язык на весь
индекс. Переименованию проектом не подлежат: у каждой свой смысл, и в первую
пишет сам `close`; `tasks.py check` проверяет состав.
4. **Форма заголовка записи** — по типу: задача отвечает на «что нужно сделать»
и пишется глаголом в неопределённой форме («Не отбрасывать молча лишние
символы»), цель — на «что приложение будет уметь», идея просто называет, о
чём она. `check` считает заголовки не в форме действия и печатает число в
блоке здоровья. Годность формулировки — не машине: её смотрит новый агент
`task-form` (форма записи, только чтение), а язык текста — `doc-wording`.
5. **Заголовки секций — с прописной, после заголовка пустая строка**, во всех
индексах. Написание канонических секций и отбивку правит `check --fix`; он
же сводит написание секции в мете файла с заголовком индекса.
6. **Язык проектных текстов** — [language.md](language.md), общий дом для
документов канона, задач, решений ADR и записок разведки: информационный
стиль (глагол вместо отглагольного существительного, активный залог, факт
вместо оценки, стоп-слова, параллельность), таблицы англицизмов и жаргона и
то, что из стиля отброшено намеренно. Проектных файлов не добавляет и
раскладку не меняет — это правила письма, а не новый слот.
7. **Умолчание профиля ревью сменилось** — это не раскладка, но проектный текст
под него уже написан. `standard` стал рабочим умолчанием: миграция схемы,
публичный контракт и инвариант ступень больше **не** поднимают, `wide`
означает новое понятие или структурную единицу. Подраздел «Триггеры профиля»
в `docs/review.md` остаётся на месте, но его содержимое надо перечитать.
**Что переехало:** `docs/tasks/PLAN.md` → `docs/tasks/ROADMAP.md`; достигнутая
цель — из небытия в секцию `Готово`: `close <цель> --implemented` удаляет файл, но
**оставляет строку с датой**. Прежде роадмап отвечал только «что осталось», и
половину его вопроса вели прозой руками. Вместе с
файлом переименован ключ конфига `tasks.plan` → `tasks.roadmap` и токены
команд: `--index plan` → `--index roadmap`, `init --plan-sections` →
`--roadmap-sections`, `init --plan` → `--roadmap`. Старый ключ в
`docs/.pm.json` не игнорируется молча — `tasks.py` останавливается и называет
переименование.
**Что удалено:** тип `[epic]`. Он был зонтиком между целью и задачами; зонтиком
стала цель, а слишком крупный шаг дробится на шаги помельче под ней. Ноль
употреблений на 97 записей двух живых проектов.
**Что сделать проекту:**
1. `git mv docs/tasks/PLAN.md docs/tasks/ROADMAP.md`.
2. Починить ссылки на прежнее имя: `grep -rn 'PLAN\.md' docs/ CLAUDE.md` —
заголовок самого файла («# План» → «# Роадмап»), строка в `docs/tasks/BACKLOG.md`,
упоминания в `docs/passport.md` и в телах задач.
3. `docs/.pm.json`: ключ `tasks.plan`, если он там был, — в `tasks.roadmap`.
4. Проставить род работы живым задачам: `python3 tasks.py check --dir docs/tasks`
перечислит те, у кого его нет. Задним числом весь беклог не переоформляется —
род нужен к взятию, так что порядок такой: сперва то, что берётся в ближайший
спринт, остальное по ходу переоценки.
5. Дописать раздел «Затрагивает» — тем же порядком и по той же причине: сперва
набор спринта, остальное по мере того, как задача попадает в работу.
6. Перечитать «Триггеры профиля» в `docs/review.md`: строки вида «миграция →
`deep`» теперь дублируют умолчание с обратным знаком. Оставить там только то,
что для этого проекта считается **новым понятием** и **правилом
идентичности**, — и убрать остальное, иначе проект возвращает себе прежнюю
частоту полного набора уточнением.
7. Переименовать секции роадмапа: `порядок` → `Запланировано`, `темы` →
`Направления`; завести `Готово` **первой** и `Разработка` последней
(порядок секций поменялся в версии 4 — если едешь сразу на неё, заводи
`Готово` последней и не переставляй дважды).
Прозаические разделы вроде «Что уже пройдено», которые велись руками,
разложить: звенья — строками в `Готово` (дата, слаг, что стало возможно),
обоснование очереди оставить прозой в `Запланировано`. Любой `##` в индексе
проверка считает секцией, и теперь `check` называет чужую секцию ошибкой.
8. Переформулировать цели ответом на **«что приложение будет уметь»**: не
«Работа со слиянием», а «Исход слияния не зависит от порядка доставки».
Свойство поведения — законная цель. Цель, которая не про приложение
(процесс, инструмент), переезжает в `Разработка`.
9. `[epic]`, если он в проекте заводился: это либо цель, либо набор задач под
общей целью. `check` назовёт его неизвестным типом.
10. Прогнать `python3 tasks.py check --dir docs/tasks --fix`: он поднимет
написание канонических секций, поставит отбивку после заголовков и сведёт
секцию в мете файлов с заголовками индексов. Секции беклога проект
переименовывает сам — их имена он выбирал, и трогать их скрипт не вправе.
11. Переписать заголовки задач в форму действия — по мере того, как задача
попадает в работу, а не «заодно»: `check` печатает их число, а `task-form`
предложит формулировки на замену пачкой.
12. Прочитать [language.md](language.md) — и **ничего не переписывать задним
числом**. Правила языка применяются к тому, что пишется и правится сейчас;
сплошная вычитка старых документов стоит дороже, чем даёт.
13. `docs/.pm.json`: `"canon": 3`.
## Версия 2 — 2026-08-03
Шапка записи ADR — мета-блоком общей формы, и у статуса появился объявленный
дом. Раскладка не менялась: правка касается одного шаблона.
**Что добавилось:** поле `- **Статус:**` в шапке `docs/adr/template.md` —
`заменено на ADR-…` либо `устарело`, у активной записи поля нет. Правило
«старая запись получает статус» было и раньше ([canon.md](canon.md), `adr/`),
но места под него шаблон не отводил: каждая запись изобретала своё, а колонка
«Статус» таблицы `adr/README.md` брала его оттуда, где он у каждого свой.
**Что переехало:** поля `Дата` и `Источник` в шаблоне стали жирными
(`- **Дата:**`, `- **Источник:**`) — та же форма, что у меты задачи и у записи
журнала дефектов: поле на строку, имя жирным.
**Что удалено:** ничего.
**Что сделать проекту:**
1. Привести `docs/adr/template.md` к скелету версии 2
([skeletons.md](skeletons.md), раздел `docs/adr/template.md`).
2. В существующих записях `docs/adr/ADR-*.md`: жирным поля шапки; если статус
записан прозой или заголовком — перенести его полем `- **Статус:**` в шапку
и сверить с колонкой «Статус» таблицы в `docs/adr/README.md`.
3. `docs/.pm.json`: `"canon": 2`.
## Версия 1 — 2026-08-03
Первая версия. Проект любой прежней раскладки приводится к ней скиллом `canon`
в режиме `adopt`, а не `upgrade`.
**Что вводится:** раскладка целиком — см. [canon.md](canon.md).
**Что сделать проекту, который приходит из свободной раскладки:**
1. `docs/.pm.json` с `{"canon": 1}` и путём миграций, если БД есть.
2. Скелет канона целиком; незаполненное — одной честной строкой.
3. `docs/specs/` разобрать: поведение — в `openspec/specs/`, обзор — в
`docs/architecture.md`, знание о чужих системах — в `docs/research/`.
Дубли capability удалить, сверив поимённо.
4. `docs/plan.md` → `docs/tasks/PLAN.md`, шаги плана — целями в «порядок».
5. `BRIEF.md` → `docs/passport.md`.
6. `docs/backlog/` → `docs/tasks/`.
7. `docs/review-journal.md` или `docs/review/journal.md` → `docs/review.md`,
плюс раздел настройки конвейера.
8. `docs/drafts/` растворить: идея → задача `[idea]`, намеренный отказ → ADR,
порядок работ → `PLAN.md`.
9. `docs/review-brief.md`, если заводился, удалить: его разделы разошлись по
документам канона.
10. `conventions.md` → `conventions/`, `local-research.md` → `research/`.
11. Завести `docs/security.md` с периметром первой строкой и `docs/adr/`.
12. В `CLAUDE.md`: severity рядом с каждым инвариантом; семантика гейта (чем
краснеет безусловно, где логи, чего в нём нет и кто тогда гоняет дорогое);
**имя основной ветки**; запреты с путями; где `testdata` и куда писать
временное; **что считается необратимым**; общий станок; ориентир по размеру
спринта. Убрать раздел «Процесс», если он пересказывает пайплайн.
13. В `openspec/config.yaml` оставить только нужды генерации и ссылки.
14. Добавить шаг `docs.py check` в гейт проекта.
**Копии правил в шаблонах, которые версия 1 уносит в проект** — их правка в
каноне обязана появляться здесь отдельной версией:
| Что копируется | Дом определения |
| --- | --- |
| форма записи журнала дефектов в `docs/review.md` | `av-dev-code/skills/review/references/review-journal.md` |
| правило заведения ADR в `docs/adr/README.md` | [canon.md](canon.md), раздел `adr/` |
@@ -0,0 +1,52 @@
# Журнал версий формата задач до слияния плагинов
**Журнал закрыт.** У каталога задач была своя версия, пока плагином его ведал
`av-dev-tasks` и ставился он отдельно. Версия теперь одна на всю раскладку —
действующий журнал [changelog.md](changelog.md), и переезд числа описан его
записью 1.
Запись ниже не переписана под нынешние имена: она описывает состояние, которое
было.
---
## Версия 1 — 2026-08-11
Первая объявленная версия формата. До неё каталог задач версии не имел вовсе:
формат менялся, а сказать, к какому его состоянию приведён конкретный проект,
было нечем — `tasks.py` о расхождении молчал, и отставший каталог выглядел
здоровым ровно до первой команды, которая об него спотыкалась.
**Что появилось.** Ключ `tasks` в `<каталог задач>/.tasks.json` — целое число,
версия формата. Сам файл стал **обязательным**: до сих пор он заводился только
ради имён, отличных от умолчания, и проект с умолчаниями жил без него. Версия —
не настройка, от которой можно отказаться, поэтому `init` и `adopt apply` теперь
пишут файл всегда, а `check` требует числа и сверяет его со своим.
**Что версия значит, а что нет.** Она отвечает на один вопрос — «по какой записи
журнала повышать каталог». Что записи применены **по существу**, из числа не
следует: двигают его руками, и соврать им так же легко, как любой другой
строкой. `check --fix` недостающее число не приписывает намеренно — это было бы
объявлением каталога приведённым к формату, шагов которого никто не делал.
**Чего в этой записи нет.** Переезды, случившиеся до появления числа, — каталог
из `docs/` в корень (канон 11) и отмена спринтов (канон 12) — задним числом сюда
не переписаны. Они уже названы журналом канона, и второй перечень тех же шагов
разошёлся бы с первым. Версия 1 — это формат на день её появления, что бы
проекту ни пришлось пройти до неё.
**Что сделать проекту.**
1. **Догнать формат по журналу канона, если каталог отстал.** Признаки известны
поимённо: каталог лежит в `docs/tasks/` (канон 11 велит `git mv docs/tasks
tasks` и починку относительных ссылок внутри записей), в нём есть `SPRINT.md`
или теги `sprint:<слаг>` (канон 12 велит снести файл, вернуть строки в беклог
через `check --fix` и расставить порядок грумингом). Ничего из этого нет —
каталог уже в сегодняшнем формате, и шаг пропускается.
2. **Завести `<каталог задач>/.tasks.json`**, если его нет. Имена частей в него
не переписываются: там только то, что отличается от умолчания.
3. **Записать версию**: `"tasks": 1` первым ключом.
4. `tasks.py check --dir <каталог задач>` — до отсутствия расхождений.
**Что при этом не трогается.** Записи в `items/`, индексы и `REJECTED.md` не
меняются ни строкой: версия 1 объявляет то, что уже есть, а не переделывает его.
+69 -777
View File
@@ -1,790 +1,82 @@
# Журнал версий канона
# Журнал версий раскладки
Одна запись на версию. Проект знает свою версию из `docs/.docs.json`; `canon
upgrade` идёт по записям снизу вверх от версии проекта до текущей и делает то,
что в них названо. Записи ниже версии 13 зовут этот файл прежним именем,
`docs/.pm.json`, — так и было на день записи, и переписывать историю мы не
станем; переименование делает запись 13.
**Каталог задач этим журналом не повышается.** У него своя версия формата и свой
журнал — `references/changelog.md` скилла `av-dev-tasks:tasks`. Записи 8, 11 и 12
трогали его в те времена, когда своего числа у него не было; впредь запись канона
вправе позвать соседа, но не двигать его версию.
Одна запись на версию. Проект знает свою версию из ключа `version` в
`.av-dev.toml`; `canon upgrade` идёт по записям снизу вверх от версии проекта до
текущей и делает то, что в них названо.
Правило записи: **что добавилось, что переехало, что удалено, что сделать
проекту**. Без последнего пункта запись бесполезна — по ней и работает
`upgrade`.
Версия — целое число. Обратной совместимости у канона нет: есть «приведён» и «не
приведён».
Версия — целое число. Обратной совместимости нет: есть «приведён» и «не
приведён». Версия **одна на всю раскладку** — и на документы канона, и на
каталог задач: ведёт их один плагин, и второе число означало бы только вопрос,
по какому журналу повышать.
**До слияния журналов было два**, и нумерация в них своя:
[changelog-before-merge.md](changelog-before-merge.md) — канон документов,
версии 114; [changelog-tasks-before-merge.md](changelog-tasks-before-merge.md) —
формат задач, версия 1. Оба **закрыты и не переписаны**: адрес, верный на день
записи, там и остался. Проект, отставший от канона 14, идёт сперва по ним, а
потом по этому журналу — порядок назван в записи 1.
---
## Версия 14 — 2026-08-11
## Версия 1 — 2026-08-13
У ADR стало два законных источника. Прежде запись цитировала только архивный
`design.md`, то есть решение, принятое по ходу изменения. Решение, принятое
**разведкой** — намеренный отказ, выбор подхода, «проверили и не делаем», — не
имеет `design.md` по построению: change по нему не заводится никогда. Триггер
канона такое решение ловит («намеренный отказ от очевидного подхода»), а дома у
него не было, и оно оседало в записке разведки или в переписке.
Три плагина — `av-dev-docs`, `av-dev-tasks` и `av-dev-code` — слились в один,
`av-dev`. Раскол делался под раздельную установку: проект мог взять учёт работ
без документов канона или конвейер без обоих. Практикой посылка не подтвердилась
— подмножество не понадобилось ни разу, — а платился раскол помеченными копиями
общих правил и ветками деградации на каждый вызов соседа.
**Что изменилось.** `adr/` принимает второй источник — записку разведки. Правило
«промоут, а не второе сочинение» не тронуто: запись по-прежнему цитирует уже
написанное и **называет источник**, изменилось только то, что источников два.
Следом сказали то же самое: карта домов, разрез проверки `doc-consistency`, вход
и устав самого агента, скелеты `docs/adr/README.md` и `docs/adr/template.md`.
**Что переехало в проекте.** Служебных файла было два, стал один:
**Почему это версия, а не правка текста.** Два следствия уезжают в репозиторий
проекта. По карте домов судит агент согласованности — прежняя редакция читала ADR
со ссылкой на записку разведки как нарушение; а скелеты `adr/` лежат в проекте
файлами и говорят там от имени канона.
**Что сделать проекту.**
1. Ничего с существующими записями: прежние ADR ссылаются на `design.md`, и это
по-прежнему верно.
2. **Поднять шапку `docs/adr/README.md`**: «промоут поверх архивного `design.md`»
→ «промоут поверх уже написанного», с обоими источниками. Точный текст — в
[skeletons.md](skeletons.md), раздел `docs/adr/README.md`.
3. **Поднять `docs/adr/template.md`**: строка `- **Источник:**` называет два
возможных источника.
4. `docs/.docs.json`: `"canon": 14`.
**Чего делать не надо.** Заводить ADR задним числом по старым разведкам. Запись
заводится, когда решение принимается, а не когда о нём вспомнили: сочинённое
через полгода обоснование — ровно то «второе сочинение», против которого правило
и написано.
---
## Версия 13 — 2026-08-11
Служебный файл канона переименован: `docs/.pm.json``docs/.docs.json`. Имя
досталось от плагина `av-dev-pm`, который распался на четыре и которого больше
нет: файл пережил владельца и указывал в пустоту. Правило простое и теперь
соблюдается всеми тремя: **имя служебного файла — имя плагина, который его
завёл**, `.docs.json` — канон, `.tasks.json` — задачи, `openspec/config.yaml`
конвейер.
**Что изменилось.** `docs.py` читает только новое имя. Прежнее он не читает
намеренно: два дома для одной версии канона расходятся молча, а тут расхождение
стоило бы дорого — по этому числу `upgrade` решает, какие записи применять.
Файл под старым именем `check` узнаёт и называет отдельной строкой с готовой
командой, а не жалуется на пропажу.
**Что появилось у соседа.** У каталога задач теперь есть **своя версия
формата** — ключ `tasks` в `<каталог задач>/.tasks.json`, — и свой журнал версий
в скилле `av-dev-tasks:tasks`. До сих пор её не было вовсе: формат задач менялся
записями этого журнала (8, 11, 12), хотя каталог принадлежит другому плагину и
ставится без канона документов. Канон это число не двигает.
**Что сделать проекту.**
1. `git mv docs/.pm.json docs/.docs.json` — одним коммитом с шагом 2. Содержимое
не меняется: ключи те же.
2. **Поправить упоминания прежнего имени** в своих файлах — `CLAUDE.md`, гейт,
`README.md`, `docs/**`. Битой ссылкой это чаще всего не выглядит (файл
служебный, на него ссылаются прозой), поэтому `docs.py check` таких упоминаний
не ловит: ищи `grep -rn '\.pm\.json'` по репозиторию.
3. **Объявить версию формата задач**, если каталог задач в проекте есть:
`<каталог задач>/.tasks.json` с ключом `"tasks": <версия>`. Файла нет вовсе —
заведи, он теперь обязателен: версия не настройка, от которой можно
отказаться. Какое число ставить и что сделать перед этим, говорит журнал
владельца — **позови скилл `av-dev-tasks:tasks`**, здесь этих шагов нет
намеренно: второй перечень чужих шагов разошёлся бы с первым.
4. Гейт не меняется: шаги те же, версию задач сторожит `tasks.py check`, который
в нём уже стоит.
5. `docs/.docs.json`: `"canon": 13`.
**Чего делать не надо.** Ключи в файле не трогаются, документы не переезжают,
записи задач не меняются: версия 13 — про имена служебных файлов и про то, кто
чью версию двигает.
---
## Версия 12 — 2026-08-09
Спринты отменены. Работа идёт задача за задачей, и замороженный набор перестал
что-либо удерживать: он отвечал на вопрос «что делать дальше», а между наборами
на этот вопрос не отвечал никто.
**Что изменилось.** Индексов задач два вместо трёх: `SPRINT.md` упразднён.
Приоритет стал тем, чем он и является, — **порядком строк в `BACKLOG.md`**:
первая строка секции это то, что делают следующим. Назначает порядок человек,
машина его не выводит; двигают его `move --after` и `move --first` с причиной.
Гейт готовности записи стоял на взятии задачи в спринт — единственном месте, где
её судили целиком. Момент нужен и без спринта: теперь это команда
`tasks.py ready <слаг>`, и зовёт её тот, кто берёт задачу в работу.
Ритуал между спринтами (`av-dev-tasks:session`) стал скиллом груминга
(`av-dev-tasks:groom`): два вопроса — что сейчас самое важное и что перестало
быть важным.
**Что сделать проекту.**
1. **Вернуть задачи из набора в беклог и снести `SPRINT.md`.** Порядок такой:
`git rm tasks/SPRINT.md`, затем `tasks.py check --dir tasks --fix`. Строки
набора после удаления файла становятся бездомными, и `--fix` возвращает их в
беклог **в конец своей секции** — с пометкой, что позицию назначает человек.
Наоборот делать нельзя: `check` без удалённого файла увидит третий индекс и
станет ругаться на него, а не чинить.
2. **Снять теги `sprint:<слаг>`** с записей — `tasks.py edit <слаг> --rm-tag
sprint:<слаг>`. Тег больше никем не читается, а `check` о нём молчит: он
законный свободный тег. Пропущенный вреда не сделает, но и пользы не несёт.
3. **Расставить порядок** — первый груминг: `av-dev-tasks:groom`. После шага 1
очередь состоит из того, что машина поставила в конец, то есть очереди нет
вовсе. Пока порядок не назначен, «что делать дальше» по-прежнему без ответа.
4. Поправить упоминания спринта в `CLAUDE.md` проекта, если они были: слот
«общий станок» переехал в груминг под именем «что считается сломанным»,
ориентир «5–8 задач в спринте» стал ориентиром размера порции разбора.
5. `docs/.pm.json`: `"canon": 12`.
**Чего делать не надо.** `REJECTED.md`, `ROADMAP.md` и файлы `items/` не
меняются: спринт жил только в собственном индексе и в тегах.
---
## Версия 11 — 2026-08-09
Каталог задач уехал из `docs/` в корень репозитория. Версия 8 отпустила его из
канона — перестала требовать, перестала проверять, — но место он занимал всё то
же, `docs/tasks/`. Полдела: каталог, принадлежащий одному плагину, лежал внутри
дерева, которым владеет другой. Проекту, поставившему учёт работ без канона
документов, приходилось заводить `docs/` ради одной вложенной папки.
**Что изменилось.** Дом задач — `tasks/` в корне репозитория. `tasks.py` ищет его
там первым; `docs/tasks/` и `doc/tasks/` остаются в списке поиска для
непереехавших проектов, а `init` заводит только в корне. Настройки — там же,
`tasks/.tasks.json`.
**Что осталось терпимым.** `docs.py` по-прежнему не считает `docs/tasks/` файлом
вне канона: непереехавший проект не должен получать выдуманную ошибку вдобавок к
этой записи, которая и так велит ему переехать.
**Что сделать проекту.**
1. `git mv docs/tasks tasks` — одним коммитом вместе с шагом 2, чтобы ссылки не
жили битыми между коммитами.
2. **Починить относительные ссылки внутри записей.** Файл `tasks/items/x.md`
стал на уровень ближе к корню: `../../passport.md` в теле записи теперь
`../docs/passport.md`. Тот же сдвиг у ссылок из индексов. Это самая тихая
часть переезда: битая относительная ссылка не мешает `tasks.py check`, её
ловит только `docs.py check` и только у документов канона.
3. Проверить ссылки **на** задачи снаружи: `CLAUDE.md`, `README.md`, гейт,
`docs/review.md`. Путь `docs/tasks/...` в них теперь ведёт в никуда.
4. Поправить путь в гейте: `tasks.py check --dir tasks`.
5. `docs/.pm.json`: `"canon": 11`.
## Версия 10 — 2026-08-09
Проверка формы `config.yaml` ушла к тому, кто файл заводит. Версия 9 перенесла в
конвейер настройку OpenSpec и честно назвала остаток: форма и сторож версии
остались в `docs.py`, то есть у файла было два плагина — один заводит, другой
проверяет. Остаток закрыт.
**Что появилось.** Скрипт `openspec.py` в скилле `av-dev-code:openspec`, две
команды: `check --dir <корень>` — форма в проекте, `form` — сверка слепка с живым
OpenSpec. Коды выхода те же, что у `docs.py` и `tasks.py`.
**Что удалено из `docs.py`.** Константы `OPENSPEC_*`, проверка формы, сторож
версии и подкоманда `openspec-form` — 252 строки. Скрипт канона про
`openspec/config.yaml` не говорит теперь ничего; `openspec/specs/` он по-прежнему
знает, потому что это дом темы `requirements` и часть карты тем.
**Что стало лучше по дороге.** Адреса `docs/passport.md` и `CLAUDE.md` требуются
теперь **только к тем документам, которые в проекте есть**. Прежняя проверка
требовала их безусловно, то есть на проекте без канона документов требовала
битую ссылку. Теперь отсутствие документа — строка «не проверялось» с указанием,
что без канона конвейер работает вслепую.
**Что осталось за каноном.** Один вопрос, и это не форма: не пересказан ли в
`context` документ, у которого есть свой дом. Разрез — утверждение, опровергаемое
открытием другого файла, против строки «открой такой-то файл»; машине он не
виден, судит агент `doc-consistency`, и `config.yaml` у него во входе.
**Что сделать проекту.**
1. Заменить в гейте и в скриптах `docs.py openspec-form` на `openspec.py form`.
Подкоманды больше нет: прежний вызов упадёт ошибкой употребления (код 2), а не
промолчит.
2. **Добавить в гейт шаг `openspec.py check`, если проект работает по OpenSpec.**
Форму раньше проверял `docs.py check` заодно; теперь он о ней молчит, и без
отдельного шага незаменённый пример в `config.yaml` перестанет ловиться. Это
главная потеря этого повышения, и она тихая.
3. Проект по OpenSpec без установленного `av-dev-code` — форму не проверяет
никто. Либо поставить плагин, либо назвать это принятым риском вслух.
4. `docs/.pm.json`: `"canon": 10`.
## Версия 9 — 2026-08-09
OpenSpec уехал в конвейер. Каталог `openspec/` версией 7 был объявлен слотом
канона: `init` его заводил, `adopt` тоже, образец `config.yaml` лежал в скелетах,
а отсутствие каталога `docs.py` считал отказом. Разрез был проведён не там. По
OpenSpec работает конвейер — без каталога не запускаются ни `opsx:propose`, ни
ревью дизайна, ни сверка требований, — а канон документов о нём только
высказывался. Проект, которому конвейер не нужен, получал отказ за отсутствие
того, чем не пользуется.
**Что появилось.** Скилл `av-dev-code: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-code` установлен, если проект работает по
OpenSpec. Без него `docs.py check` про каталог промолчит — и молчание это
законное, так что отсутствие настройки перестанет ловиться само.
3. Проект **не** работает по OpenSpec: убедиться, что `openspec/` нет, и
перестать держать его пустым ради проверки. Она больше не требует каталога.
4. `docs/.pm.json`: `"canon": 9`.
## Версия 8 — 2026-08-09
Канон отпустил каталог задач. Плагин `av-dev-pm` расколот на `av-dev-docs`
(документы) и `av-dev-tasks` (учёт работ), и каждый теперь ставится сам по себе.
Пока владелец был один, `docs/tasks/` числился слотом канона: `docs.py` требовал
каталог, звал внутрь чужой скрипт и выдавал его дрейф за свой, а настройки задач
жили ключом `tasks` в `docs/.pm.json`. Для проекта, поставившего только документы,
всё это — отказ на ровном месте: задач он не ведёт, и требовать их не за что.
**Что изменилось.** Каталог задач канону не принадлежит; канон резервирует ему
место в `docs/` и внутрь не смотрит. `docs.py` больше не проверяет согласованность
задач вовсе — это делает `tasks.py` сам, командой своего плагина. Дом настроек
каталога задач — `<каталог задач>/.tasks.json`; ключ `tasks` в `docs/.pm.json`
читается, только пока своего файла нет, и об этом говорится замечанием.
**Что удалено.** Проверка `check_tasks` из `docs.py` и ключ `"tasks"` из скелета
`docs/.pm.json`.
**Что сделать проекту.**
1. Перенести настройки задач: содержимое ключа `"tasks"` из `docs/.pm.json` — в
`docs/tasks/.tasks.json` тем же объектом. Ключа в проекте нет (имена файлов
и заголовков умолчательные) — переносить нечего, шаг пропускается.
2. Удалить ключ `"tasks"` из `docs/.pm.json` после переноса. Оставленный он не
читается, и `tasks.py` скажет об этом замечанием на каждом прогоне.
3. Проверить, что согласованность задач по-прежнему кто-то гоняет: раньше её
тянул за собой `docs.py check`, теперь — только `tasks.py check`. **Если в
гейте проекта стоял один `docs.py`, добавить туда второй шаг** — иначе дрейф
индексов перестанет ловиться молча, и это самая вероятная потеря на этом
повышении.
4. Установить оба плагина, если нужны оба: `av-dev-docs` и `av-dev-tasks`
вместо прежнего `av-dev-pm`. Прежний из `enabledPlugins` убрать.
5. `docs/.pm.json`: `"canon": 8`.
## Версия 7 — 2026-08-07
`openspec/` был предпосылкой, о которой канон говорил, но за которой не следил.
Каталог назван в раскладке, `openspec/specs/` объявлен домом темы `requirements`,
`config.yaml` описан абзацем — а заводил всё это человек руками, и проверялось
из перечисленного ничего. Заведение нового проекта проходило мимо: `init`
собирал документы канона и оставлял проект без каталога, без которого не работают
ни `opsx:propose`, ни ревью дизайна, ни сверка требований.
Хуже отсутствия оказался файл из коробки. `openspec init` кладёт `config.yaml`,
где `context` и `rules` — закомментированный пример на английском. Такой файл
читается как настроенный: он есть, он валиден, имя правильное. Работает он как
пустой, и узнаётся это по предложению, написанному на другом языке, с
capability по имени пакета и без единого `SHALL`.
**Что изменилось:**
1. **`init` заводит OpenSpec сам** — `openspec init --tools claude`, до первого
документа канона. Команда названа в каноне поимённо, потому что её печатает
отказ `docs.py`.
2. **У `openspec/config.yaml` появилась каноническая форма** и скелет в
`skeletons.md`. Содержание — только то, что нужно **в момент порождения
артефакта**: язык, правила именования capability, придирки валидатора и
**адреса** документов канона. Пересказ паспорта, инвариантов, конвенций и
правил ревью в него не переносится.
3. **`docs.py check` проверяет пять вещей:** каталог `openspec/` есть; файл
называется `config.yaml` (`config.yml` OpenSpec читать не станет и об этом не
сообщит); `context` и `rules.specs` не остались примером, а правила для
`specs` называют `SHALL`; `context` называет `passport` и `CLAUDE.md`; ключи
под `rules:` — имена артефактов схемы, а не опечатки.
4. **За свежестью формы следит машина, а не память.** Схема и перечень
артефактов — слепок чужого инструмента; `check` сравнивает `major.minor`
установленного OpenSpec с версией, на которой форма сверялась, и при
расхождении даёт замечание. Перепроверяет `docs.py openspec-form`, и чинится
расхождение **в плагине, а не в проекте**.
5. **Шестое проверяет агент.** Отличить ссылку на документ от пересказа документа
машина не умеет — это работа `doc-consistency`, и в таблице «Что проверяет
машина, а что человек» она стоит строкой.
**Что переехало:** ничего в раскладке `docs/`. Ни один файл не переименовывается
и не перемещается.
**Что сделать проекту:**
1. Нет `openspec/` — завести: `openspec init --tools claude`. Команда кладёт ещё
и `.claude/skills/openspec-*` с `.claude/commands/opsx/*`; это её нормальная
работа, удалять их не надо.
2. Открыть `openspec/config.yaml` и привести к скелету из
[skeletons.md](skeletons.md): блок `context` с языком, правилами именования
capability, требованием `SHALL` и **адресами** `docs/passport.md` и
`CLAUDE.md`; блок `rules` с четырьмя правилами для `specs`.
3. **Вычистить из `context` пересказ.** Инварианты, перечень конвенций, состав
шагов гейта, правило выбора метки и состав проходов ревью — заменить ссылкой
на дом. Признак пересказа простой: строку можно опровергнуть, открыв другой
файл проекта.
4. Проверить имя файла: `config.yml` переименовать в `config.yaml`. Если жили оба
— содержимое `.yml` до сих пор не читалось никем, и переносить из него нужно
именно то, чего нет в `.yaml`.
5. `docs/.pm.json`: `"canon": 7`.
---
## Версия 6 — 2026-08-07
Версия 5 объявила: **каждый документ `docs/` — тема ревью**. Правило оказалось
верным ровно наполовину и потому вредным целиком. Паспорт и схему хранилища
ревью читает, но темами они не являются — они задают границу, по которой судит
чужая тема. Журнал решений и журнал наблюдений ревью изменения не нужны вовсе:
ADR объясняет прошлое решение, а не предъявляет требование к изменению.
Разметчик, применявший плоское правило буквально, обязан был либо завести
фантомные темы `passport`, `adr`, `database`, `research` и продублировать ими
работу тем `architecture` и `operations`, либо потерять четыре документа молча —
а молчащая потеря и есть то, против чего канон написан.
**Что изменилось:**
1. **Три категории документов вместо одной.** Разрез проверяемый: можно ли по
документу сказать «в этом изменении сделано не так»? **Тема** — да, прямо
(`conventions`, `security`, `architecture`, свои документы проекта).
**Источник темы** — нет, но он задаёт границу для чужой темы (`passport.*` →
`architecture`, `database.*` → `operations`, `CLAUDE.md` → `autotests`,
`openspec/specs/` → `requirements`). **Процессный документ** — нет, он про то,
как мы работаем (`tasks/`, `review.*`, `adr.*`, `research.*`, `.pm.json`).
2. **Категории `источник` и `процессный` закрыты, категория `тема` открыта.**
Прежде открытым был весь список, и «не темы ровно две» противоречило
собственной раскладке канона. Теперь пополняется только одно множество, и
документ, которого нет в раскладке, — однозначно своя тема проекта.
3. **`adr/` и `research/` уходят из входа ревью изменения.** Прогон их больше не
открывает. Проверяться они не перестали: ADR без ссылки на архивный
`design.md`, замена без парного статуса, число без провенанса — это по-прежнему
работа `doc-consistency` и `doc-code-drift`, на сессии между спринтами.
4. **`docs.py` печатает категорию в отказе.** «Нет источника passport» читается
иначе, чем «нет темы security». Обязательность при этом не изменилась:
заводятся все документы одинаково и с первого дня.
5. **У задачи появилась метка — `small`, `medium` или `large`.** Это итог
классификации и **единственный вход, по которому конвейер выбирает
исполнителей** на обеих стадиях ревью. Прежние имена `quick`, `standard` и
`wide` описывали глубину прогона, то есть свойство ревью; метка описывает
**задачу** — а выбирают по ней одно и то же. Слово «ступень» уходит:
у одной вещи одно имя.
6. **Метка выводится из двух осей и не равна ни одной из них.** Размер (малое,
среднее, крупное) и сложность (знакомое, незнакомое); метка — максимум по
ним. Малое **незнакомое** изменение получает `large`, трогая один узел, —
поэтому размер и метка пишутся отдельными строками, и выводить одно из
другого нельзя.
**Цена, записанная явно:** расхождение изменения с записанным решением прогоном
больше не ловится. Раньше архитектурный проход мог сказать «здесь отменено
решение ADR-2026-03-11, парного статуса нет»; теперь это скажет только сверка
документации. Сделка сознательная: чтение всего каталога решений оплачивалось на
каждой задаче, а срабатывало на единицах.
**Что переехало:** ничего в раскладке. Ни один файл не переименовывается и не
перемещается.
**Что сделать проекту:**
1. `docs/review.*`, подраздел «Вопросы по темам»: убрать вопросы, адресованные
`passport`, `database`, `adr`, `research` и `review` — **ни одно из этих имён
больше не тема**. Под каноном 5 темой был каждый документ `docs/`, поэтому
такие вопросы там законны и почти наверняка есть. Переадресовать:
про границу домена и про решение → `architecture`; про хранилище, настройку и
измеренное число → `operations`. Вопрос, который никуда не переадресовывается,
удалить, а не оставить висеть: адресованный несуществующей теме, он не
задаётся никем и молча.
2. Там же, «Недоступно проверке»: те же пять имён убрать из разнесения по темам,
переразнеся содержимое по оставшимся.
3. Там же: подраздел **«Триггеры профиля» → «Триггеры метки»**, и разнести его
на **три** списка вместо двух — «крупное здесь» (про объём), «незнакомое
здесь» (про форму решения) и «мелкое здесь» (опускает до `small`). Раньше
первые две оси были склеены в один список, и потому объём в правило по факту
не входил.
4. **Переименовать метки прогона везде, где проект их называет** — в «Триггерах
метки», в «Недоступно проверке», в журнале дефектов: `quick` → **`small`**,
`standard` → **`medium`**, `wide` → **`large`**. Метка это итог классификации
задачи, и три её значения — часть общего словаря канона и конвейера. Слово
«ступень» из документов уходит: у одной вещи одно имя.
5. Проверить, что свои темы проекта не совпадают именем с закрытыми категориями:
`docs/passport/`, `docs/adr/`, `docs/research/`, `docs/database/`,
`docs/review/` — это слоты канона, а не свои темы, и своим смыслом их
наполнять нельзя.
6. Ничего не заводить и не удалять: раскладка канона 6 совпадает с раскладкой
канона 5 файл в файл.
7. `docs/.pm.json`: `"canon": 6`.
---
## Версия 5 — 2026-08-06
Канон перестал быть списком файлов и стал **списком тем ревью**. Раскладка та же,
но читается иначе: документ в `docs/` — это направление проверки, а не просто
текст. Отсюда три правки, и все три развязывают то, что раньше было жёстко
сцеплено.
**Что изменилось:**
1. **Тема живёт файлом или каталогом, на выбор проекта.** `docs/security.md` и
`docs/security/` — одно и то же; тема разрослась, стала каталогом с
`README.md` — канон не сменился и версия не двинулась. Прежде форма была
задана поимённо: `conventions`, `research` и `adr` обязаны были быть
каталогами, остальные — файлами, и обосновать это было нечем. Обе формы сразу
— ошибка: два дома для одного факта расходятся молча.
2. **Список тем открытый.** Всё, что проект кладёт в `docs/`, становится темой
ревью и попадает в план каждого прогона; именной оптики у такой темы нет, её
разбирает общий проход конвейера, заведённый ровно за этим.
Прежде `docs.py` называл незнакомый файл «вне канона» — теперь называет своей
темой проекта и перечисляет их в отчёте. Не темы ровно две: `docs/tasks/` и
`docs/review.*`.
3. **`AGENTS.md` рядом с `CLAUDE.md` — законно.** Он почти стандарт; обязателен
по-прежнему только `CLAUDE.md`, но если лежат оба, читаются оба, и проверки
канона смотрят на второй так же, как на первый.
**Что переехало:**
- в `docs/review.*`: **«Вопросы к проходам» → «Вопросы по темам»**, форма
`<тема>: <вопрос> (<провенанс>)`. Причина не косметическая: вопрос,
адресованный проходу, перестал задаваться молча в тот день, когда тот уехал в
верхнюю ступень ревью. Тема переезд прохода переживает, имя прохода — нет;
- там же **«Недоступно проверке» — по темам**, оба подраздела.
**Что сделать проекту:**
1. Ничего не переименовывать, если всё уже разложено по канону 4: обе формы
дома законны, и текущая — одна из них.
2. `docs/review.*`, подраздел «Вопросы к проходам»: переименовать в «Вопросы по
темам» и переадресовать каждый вопрос теме вместо имени прохода. Темы ядра —
`requirements`, `autotests`, `conventions`, `architecture`, `security`,
`operations`.
3. Там же «Недоступно проверке»: разнести обе половины по темам.
4. Проверить, не лежит ли в `docs/` документ, который раньше считался лишним и
потому не заводился. Теперь он законен и станет темой ревью — это и есть
способ добавить проверку, которой в конвейере нет.
5. `docs/.pm.json`: `"canon": 5`.
6. Позвать судей `doc-consistency` и `doc-code-drift` — шагом 6 `upgrade`.
## Версия 4 — 2026-08-05
Две правки, обе про то, как читается каталог задач. Первая — секция роадмапа
переименована, и вместе с именем расширен её смысл; достигнутое переехало вниз.
Вторая — **у каждой записи появился тип, и тип определяет, что с записью можно
делать**. Раскладка не меняется, файлов канона не прибавляется.
**Что переехало:**
- секция роадмапа `Разработка` → **`Сопровождение`** (англ. `Tooling` →
**`Operations`**). Прежнее имя называло слишком много: роадмап **весь** про
разработку, и секция с таким именем не отличалась от остальных ничем;
- **тип записи** — из префикса заголовка (`[goal]`/`[idea]`) и тега
`kind:<род>` в **поле меты `Тип`** первой строкой. Эмодзи в заголовке от него
производна;
- **поле места** у задачи: `Секция` → **`Категория`**. У цели остаётся `Секция`:
у задачи поле называет полку домена, в которую она вернётся из спринта, у цели
— часть роадмапа, то есть состояние очереди. Одно имя на два смысла их и
смешивало.
**Что добавилось:**
1. **Смысл секции расширен.** Было «инструмент и процесс», стало «чем держат
проект: инструмент, процесс, эксплуатация». Метрики, логи, инфраструктура,
выкладка и дежурство — сюда же. Расширение не косметическое: английское
`Operations` при узком смысле обещало бы эксплуатацию, а внутри лежал бы
линтер.
2. **Общий словарь трёх мест** — [canon.md](canon.md), раздел «Сопровождение и
эксплуатация». Сопровождение — всё, чем держат проект; эксплуатация — его
часть, работа системы на проде. `ROADMAP.md`, секция `Сопровождение` — план
работ; `architecture.md`, раздел «Эксплуатация» — как устроено сейчас;
эксплуатационный проход ревью — оптика проверки. Слить их в одно слово
нельзя: они отвечают на разные вопросы. Слово **«поддержка» не употребляется
вовсе** — в нём слышится помощь пользователю.
3. **Граница с возможностями проходит по тому, кто наблюдает.** «Приложение
сообщает о своём состоянии» — возможность приложения, её место среди прочих
целей. «Дежурный видит состояние на одном экране» — сопровождение. Одни и те
же метрики попадают в разные секции роадмапа, и это верно.
4. **Порядок секций стал каноническим**, и `Готово` переехало **вниз**:
`Запланировано` | `Направления` | `Сопровождение` | `Готово`. Достигнутое
копится — через год этой секции больше, чем всех остальных вместе, — и стоя
первой она отодвигает за экран то, ради чего роадмап открывают чаще всего.
Порядок проверяет `tasks.py check`, переставляет `check --fix`.
5. **Заголовок секции отбивается пустой строкой с обеих сторон.** Прежде
проверялась только строка после заголовка; перестановка секций двигает целые
блоки, и два заголовка оказываются вплотную. Правит `check --fix`.
6. **Тип — единственная ось записи, закрытый словарь из пяти значений:**
`goal` | `feature` | `fix` | `chore` | `research`. Осей было две — тип записи
(`goal`/`idea`/`task`) и род работы (`kind:` тегом), — но из двенадцати
клеток произведения законны были шесть, а алгоритм работы крепится к роду, а
не к типу. Оси схлопнуты.
7. **Тип задаёт схему тела:** какие разделы обязательны, какие допустимы, нужна
ли цель, берётся ли запись в спринт. Проверяет `sprint take`, замечания даёт
`check`. Два раздела новые: **`Воспроизведение`** у `fix` (не
воспроизводится — это `research`, а не `fix`; правило было записано и не
проверялось) и **`Вопрос` + `Куда ляжет ответ`** у `research` вместо
критериев приёмки (приёмка разведки — записанный ответ, и критерии в форме
«оракул: тест» ей натянуты).
8. **Тип `idea` упразднён.** Он значил не род работы, а состояние
незаполненности, а состояние типом быть не может. Теперь оно называется
честно: `research` без раздела «Вопрос» — **сырьё**. В спринт не берётся, как
и прежняя идея, лежит **в конце своей категории** (проверяет `check`,
переставляет `--fix`) и отбирается `list --raw`. Порядка «по важности» в
беклоге по-прежнему нет: этот порядок производен от типа, а не назначен
человеком.
9. **Алгоритм работы над каждым типом** — отдельным файлом,
`skills/tasks/references/task-<тип>.md`: схема, что проверяет машина, что
человек, и порядок шагов.
10. **Имена файлов проверяются.** Правило «текст русский, имена английские»
стояло в каноне и не было подкреплено ничем: `docs.py` имён не смотрел вовсе.
Теперь смотрит — кириллица и не-kebab-case **жёстко**, форма имени
`ADR-ГГГГ-ММ-ДД-slug.md` жёстко, транслит **эвристикой**, то есть
замечанием. Заодно из раскладки канона убраны плейсхолдеры `<тема>.md`,
приглашавшие называть файлы по-русски.
11. **Два агента вместо обещания.** В каноне была таблица «Что проверяет машина,
а что человек», и её правая колонка три версии описывала судью, которого не
существовало. Судьи заведены и разведены по глубине: **`doc-consistency`**
(документ ↔ документ ↔ openspec: факт в двух домах, прямое противоречие,
поведение в `architecture.md` вместо спек, ADR без ссылки и парного статуса,
число без провенанса, заглушка вместо честной строки); **`doc-code-drift`**
(документ ↔ код по закрытому перечню фактов). Оба зовутся раз в спринт на
сессии, а также после adopt и после upgrade, на весь канон разом.
**Что сделать проекту:**
1. Переименовать заголовок секции в `docs/tasks/ROADMAP.md`: `## Разработка` →
`## Сопровождение` (или `## Tooling` → `## Operations`, если индекс
английский). **`check --fix` этого не сделает**: регистр канонической секции
он правит сам, а чужую секцию только называет ошибкой — смысл за человеком.
2. Поправить поле `- **Секция:**` в файлах целей, которые в ней лежат. Порядок
именно такой: сперва заголовок, потом `python3 tasks.py check --dir
docs/tasks` покажет расхождение поимённо.
3. Перечитать состав секции: цели про выкладку, метрики, логи и инфраструктуру,
если они лежали в `Направлениях` за неимением места, переезжают сюда.
4. Прогнать `python3 tasks.py check --dir docs/tasks --fix`. За один проход он
переставит секции роадмапа в канонический порядок (`Готово` уедет вниз вместе
со всем содержимым), поправит отбивку заголовков и **переведёт записи на
типы**: перенесёт значение из тега `kind:` и префикса `[goal]`/`[idea]` в поле
`Тип`, снимет тег, поставит эмодзи в заголовок, переименует `Секция` →
`Категория` у задач и снесёт сырьё в конец категорий.
5. Разобрать то, что `--fix` вернул пометкой `НЕОДНОЗНАЧНО`. Главный случай —
**записи без типа**: заведённые до появления рода работы, они не несут ни
тега, ни префикса, и машина их не угадывает (`feature` от `chore` не
отличает). Проставить руками: `edit <слаг> --type …`.
6. Дописать новые обязательные разделы у задач, которые собираются в спринт:
`Воспроизведение` у каждого `fix`, `Вопрос` и `Куда ляжет ответ` у каждого
`research`. Не «заодно по всему беклогу», а порциями переоценки: `check`
ошибкой это не считает, отказывает только `sprint take`. Сколько задач готово
к взятию, печатает блок здоровья `check`.
7. Прогнать `python3 docs.py check`: он назовёт имена файлов не по правилу.
Кириллицу и не-kebab-case править обязательно, транслит — по решению
человека. **Переименование ADR это перенос ссылок**: слаг стоит в
`adr/README.md`, в `architecture.md` и в чужих документах, и делается одним
проходом, иначе останутся битые ссылки (их `docs.py` потом и покажет).
8. `docs/review.md`, подраздел «Триггеры профиля» — переписать целиком, он
отстал дважды. Снести перечень мест для `deep`: профиль упразднён вместе с
проходом независимой реализации, и перечень стал указателем в пустоту.
Оставшийся перечень перевести на новое правило: `wide` теперь означает не
«новое понятие», а **крупное или незнакомое** изменение и рассчитан на 5–10%
задач; отдельным списком назвать, что здесь считается **мелким** (это `quick`).
Форма подраздела — в [skeletons.md](skeletons.md). Там же проверить журнал
дефектов и «Недоступно проверке» на упоминания независимой реализации: класс
«форма решения, где спека выбора не сделала» переезжает в подраздел «перестали
проверять сознательно», а рядом с ним встаёт вторая честная строка — на
`quick` и `standard` не проверяется ничего, что требует запуска.
9. `docs/.pm.json`: `"canon": 4`.
10. Позвать **обоих судей** — `doc-consistency` и `doc-code-drift`, шагом 6
`upgrade`. Пунктов выше десять, половина из них ручная, и именно здесь видно,
какие сделаны только наполовину: переименования секций и полей разводят
документы, а `check` сверяет число версии, а не существо. Первый прогон на
живом проекте вдобавок самый урожайный — правило единственного дома до сих пор
никто не проверял. Разбирать порциями, а не одним заходом.
## Версия 3 — 2026-08-04
Роадмап стал **состоянием проекта**, а не очередью работ: цель — возможность
приложения, задача — шаг к ней, достигнутое из роадмапа не исчезает. Плюс род
работы, раздел «Затрагивает» и новое умолчание профиля ревью. Раскладка меняется
в одном файле, но переименование и смена секций тянут за собой ссылки, поэтому
шаги делаются одним заходом.
**Что добавилось:**
1. **Род работы** — тег `kind:<род>` в мете задачи, словарь закрыт:
`feature` | `fix` | `chore` | `research`. Обязателен у задачи, у цели
запрещён. `sprint take` без него отказывает, `check` о пропаже напоминает
замечанием. Определение — [canon.md](canon.md), раздел `tasks/`; смысл и
причина, почему тегом, — в SKILL.md скилла `tasks`, раздел «Род работы».
2. **Раздел «Затрагивает»** в теле задачи — перечень границ, которых изменение
касается (эндпоинт, таблица и миграция, формат на диске, публичный тип). Как
и критерии приёмки, требуется к взятию в спринт, а не к заведению.
3. **Секции роадмапа** — четыре вместо двух и **канонические**, в отличие от
секций беклога: `Готово` (достигнутые цели строкой с датой, без ссылки на
файл), `Запланировано` (очередь значима), `Направления` (очереди нет),
`Разработка` (инструмент и процесс, не возможности приложения). Английский
вариант — `Done` | `Planned` | `Directions` | `Tooling`, один язык на весь
индекс. Переименованию проектом не подлежат: у каждой свой смысл, и в первую
пишет сам `close`; `tasks.py check` проверяет состав.
4. **Форма заголовка записи** — по типу: задача отвечает на «что нужно сделать»
и пишется глаголом в неопределённой форме («Не отбрасывать молча лишние
символы»), цель — на «что приложение будет уметь», идея просто называет, о
чём она. `check` считает заголовки не в форме действия и печатает число в
блоке здоровья. Годность формулировки — не машине: её смотрит новый агент
`task-form` (форма записи, только чтение), а язык текста — `doc-wording`.
5. **Заголовки секций — с прописной, после заголовка пустая строка**, во всех
индексах. Написание канонических секций и отбивку правит `check --fix`; он
же сводит написание секции в мете файла с заголовком индекса.
6. **Язык проектных текстов** — [language.md](language.md), общий дом для
документов канона, задач, решений ADR и записок разведки: информационный
стиль (глагол вместо отглагольного существительного, активный залог, факт
вместо оценки, стоп-слова, параллельность), таблицы англицизмов и жаргона и
то, что из стиля отброшено намеренно. Проектных файлов не добавляет и
раскладку не меняет — это правила письма, а не новый слот.
7. **Умолчание профиля ревью сменилось** — это не раскладка, но проектный текст
под него уже написан. `standard` стал рабочим умолчанием: миграция схемы,
публичный контракт и инвариант ступень больше **не** поднимают, `wide`
означает новое понятие или структурную единицу. Подраздел «Триггеры профиля»
в `docs/review.md` остаётся на месте, но его содержимое надо перечитать.
**Что переехало:** `docs/tasks/PLAN.md` → `docs/tasks/ROADMAP.md`; достигнутая
цель — из небытия в секцию `Готово`: `close <цель> --implemented` удаляет файл, но
**оставляет строку с датой**. Прежде роадмап отвечал только «что осталось», и
половину его вопроса вели прозой руками. Вместе с
файлом переименован ключ конфига `tasks.plan` → `tasks.roadmap` и токены
команд: `--index plan` → `--index roadmap`, `init --plan-sections` →
`--roadmap-sections`, `init --plan` → `--roadmap`. Старый ключ в
`docs/.pm.json` не игнорируется молча — `tasks.py` останавливается и называет
переименование.
**Что удалено:** тип `[epic]`. Он был зонтиком между целью и задачами; зонтиком
стала цель, а слишком крупный шаг дробится на шаги помельче под ней. Ноль
употреблений на 97 записей двух живых проектов.
**Что сделать проекту:**
1. `git mv docs/tasks/PLAN.md docs/tasks/ROADMAP.md`.
2. Починить ссылки на прежнее имя: `grep -rn 'PLAN\.md' docs/ CLAUDE.md` —
заголовок самого файла («# План» → «# Роадмап»), строка в `docs/tasks/BACKLOG.md`,
упоминания в `docs/passport.md` и в телах задач.
3. `docs/.pm.json`: ключ `tasks.plan`, если он там был, — в `tasks.roadmap`.
4. Проставить род работы живым задачам: `python3 tasks.py check --dir docs/tasks`
перечислит те, у кого его нет. Задним числом весь беклог не переоформляется —
род нужен к взятию, так что порядок такой: сперва то, что берётся в ближайший
спринт, остальное по ходу переоценки.
5. Дописать раздел «Затрагивает» — тем же порядком и по той же причине: сперва
набор спринта, остальное по мере того, как задача попадает в работу.
6. Перечитать «Триггеры профиля» в `docs/review.md`: строки вида «миграция →
`deep`» теперь дублируют умолчание с обратным знаком. Оставить там только то,
что для этого проекта считается **новым понятием** и **правилом
идентичности**, — и убрать остальное, иначе проект возвращает себе прежнюю
частоту полного набора уточнением.
7. Переименовать секции роадмапа: `порядок` → `Запланировано`, `темы` →
`Направления`; завести `Готово` **первой** и `Разработка` последней
(порядок секций поменялся в версии 4 — если едешь сразу на неё, заводи
`Готово` последней и не переставляй дважды).
Прозаические разделы вроде «Что уже пройдено», которые велись руками,
разложить: звенья — строками в `Готово` (дата, слаг, что стало возможно),
обоснование очереди оставить прозой в `Запланировано`. Любой `##` в индексе
проверка считает секцией, и теперь `check` называет чужую секцию ошибкой.
8. Переформулировать цели ответом на **«что приложение будет уметь»**: не
«Работа со слиянием», а «Исход слияния не зависит от порядка доставки».
Свойство поведения — законная цель. Цель, которая не про приложение
(процесс, инструмент), переезжает в `Разработка`.
9. `[epic]`, если он в проекте заводился: это либо цель, либо набор задач под
общей целью. `check` назовёт его неизвестным типом.
10. Прогнать `python3 tasks.py check --dir docs/tasks --fix`: он поднимет
написание канонических секций, поставит отбивку после заголовков и сведёт
секцию в мете файлов с заголовками индексов. Секции беклога проект
переименовывает сам — их имена он выбирал, и трогать их скрипт не вправе.
11. Переписать заголовки задач в форму действия — по мере того, как задача
попадает в работу, а не «заодно»: `check` печатает их число, а `task-form`
предложит формулировки на замену пачкой.
12. Прочитать [language.md](language.md) — и **ничего не переписывать задним
числом**. Правила языка применяются к тому, что пишется и правится сейчас;
сплошная вычитка старых документов стоит дороже, чем даёт.
13. `docs/.pm.json`: `"canon": 3`.
## Версия 2 — 2026-08-03
Шапка записи ADR — мета-блоком общей формы, и у статуса появился объявленный
дом. Раскладка не менялась: правка касается одного шаблона.
**Что добавилось:** поле `- **Статус:**` в шапке `docs/adr/template.md` —
`заменено на ADR-…` либо `устарело`, у активной записи поля нет. Правило
«старая запись получает статус» было и раньше ([canon.md](canon.md), `adr/`),
но места под него шаблон не отводил: каждая запись изобретала своё, а колонка
«Статус» таблицы `adr/README.md` брала его оттуда, где он у каждого свой.
**Что переехало:** поля `Дата` и `Источник` в шаблоне стали жирными
(`- **Дата:**`, `- **Источник:**`) — та же форма, что у меты задачи и у записи
журнала дефектов: поле на строку, имя жирным.
**Что удалено:** ничего.
**Что сделать проекту:**
1. Привести `docs/adr/template.md` к скелету версии 2
([skeletons.md](skeletons.md), раздел `docs/adr/template.md`).
2. В существующих записях `docs/adr/ADR-*.md`: жирным поля шапки; если статус
записан прозой или заголовком — перенести его полем `- **Статус:**` в шапку
и сверить с колонкой «Статус» таблицы в `docs/adr/README.md`.
3. `docs/.pm.json`: `"canon": 2`.
## Версия 1 — 2026-08-03
Первая версия. Проект любой прежней раскладки приводится к ней скиллом `canon`
в режиме `adopt`, а не `upgrade`.
**Что вводится:** раскладка целиком — см. [canon.md](canon.md).
**Что сделать проекту, который приходит из свободной раскладки:**
1. `docs/.pm.json` с `{"canon": 1}` и путём миграций, если БД есть.
2. Скелет канона целиком; незаполненное — одной честной строкой.
3. `docs/specs/` разобрать: поведение — в `openspec/specs/`, обзор — в
`docs/architecture.md`, знание о чужих системах — в `docs/research/`.
Дубли capability удалить, сверив поимённо.
4. `docs/plan.md` → `docs/tasks/PLAN.md`, шаги плана — целями в «порядок».
5. `BRIEF.md` → `docs/passport.md`.
6. `docs/backlog/` → `docs/tasks/`.
7. `docs/review-journal.md` или `docs/review/journal.md` → `docs/review.md`,
плюс раздел настройки конвейера.
8. `docs/drafts/` растворить: идея → задача `[idea]`, намеренный отказ → ADR,
порядок работ → `PLAN.md`.
9. `docs/review-brief.md`, если заводился, удалить: его разделы разошлись по
документам канона.
10. `conventions.md` → `conventions/`, `local-research.md` → `research/`.
11. Завести `docs/security.md` с периметром первой строкой и `docs/adr/`.
12. В `CLAUDE.md`: severity рядом с каждым инвариантом; семантика гейта (чем
краснеет безусловно, где логи, чего в нём нет и кто тогда гоняет дорогое);
**имя основной ветки**; запреты с путями; где `testdata` и куда писать
временное; **что считается необратимым**; общий станок; ориентир по размеру
спринта. Убрать раздел «Процесс», если он пересказывает пайплайн.
13. В `openspec/config.yaml` оставить только нужды генерации и ссылки.
14. Добавить шаг `docs.py check` в гейт проекта.
**Копии правил в шаблонах, которые версия 1 уносит в проект** — их правка в
каноне обязана появляться здесь отдельной версией:
| Что копируется | Дом определения |
| Было | Стало |
| --- | --- |
| форма записи журнала дефектов в `docs/review.md` | `av-dev-code/skills/review/references/review-journal.md` |
| правило заведения ADR в `docs/adr/README.md` | [canon.md](canon.md), раздел `adr/` |
| `docs/.docs.json`, ключ `canon` | `.av-dev.toml` в корне, ключ `version` |
| `docs/.docs.json`, ключ `migrations` | `.av-dev.toml`, секция `[docs]` |
| `<каталог задач>/.tasks.json`, ключ `tasks` | тот же `version`: версия теперь одна |
| `<каталог задач>/.tasks.json`, имена частей | `.av-dev.toml`, секция `[tasks]` |
Корень выбран потому, что он есть у обоих: и у проекта без `docs/`, и у проекта
без каталога задач. Формат TOML — ради комментариев: файл лежит в репозитории
проекта, и назначение числа читают из него самого, а не из документации плагина.
**Что переехало в вызовах.** Имена скиллов сменили пространство имён и получили
префикс по прежнему плагину: `av-dev-docs:canon``av-dev:doc-canon`,
`av-dev-docs:init``av-dev:doc-init`, `av-dev-docs:docs``av-dev:doc-sync`,
`av-dev-docs:healthcheck``av-dev:doc-healthcheck`, `av-dev-tasks:tasks`
`av-dev:task-track`, `av-dev-tasks:groom``av-dev:task-groom`,
`av-dev-code:openspec``av-dev:code-openspec`, `av-dev-code:resolve`
`av-dev:code-resolve`, `av-dev-code:review``av-dev:code-review`.
**Что сделать проекту.**
1. **Отставшим сперва прежние журналы.** Версия канона в `docs/.docs.json`
меньше 14 — пройди записи до 14 по
[changelog-before-merge.md](changelog-before-merge.md), и только потом эту.
Иначе повышение объявит приведённым то, чего никто не делал.
2. **Завести `.av-dev.toml`** в корне репозитория: `version = 1`, секция
`[docs]` с `migrations`, если ключ был, секция `[tasks]` с `dir` и теми
именами частей, которые в `.tasks.json` отличались от умолчаний. Комментарии
пиши свои — файл читает человек.
3. **Удалить `docs/.docs.json` и `<каталог задач>/.tasks.json`.** Прежние имена
не читаются: два дома для одной версии расходятся молча. Пока старые файлы на
месте, `docs.py check` и `tasks.py check` называют это прежней раскладкой.
4. **Переставить плагины.** `av-dev-docs`, `av-dev-tasks` и `av-dev-code`
удалить, `av-dev` поставить — команды в README репозитория плагинов.
5. **Поправить гейт проекта.** Пути к `docs.py`, `tasks.py` и `openspec.py`
сменились вместе с именами каталогов скиллов: `skills/canon/`
`skills/doc-canon/`, `skills/tasks/``skills/task-track/`,
`skills/openspec/``skills/code-openspec/`. Шаг, который не нашёл скрипт,
обязан краснеть, а не пропускаться, — проверь, что он краснеет.
6. **Поправить свои вызовы скиллов** — в `CLAUDE.md`, в `Taskfile`, в записях
задач: короткое имя разрешится в проектную копию, а прежнее полное не
разрешится вовсе.
7. `docs.py check` и `tasks.py check --dir <каталог задач>` — до отсутствия
дрейфа.
**Чего делать не надо.** Переписывать прошлые записи журналов под новые имена.
Они описывают состояния, которые были, и адрес, верный на день записи, остаётся
верным как свидетельство.
+24 -16
View File
@@ -117,7 +117,7 @@
со строкой «запись лежит сжатой и распаковывается целиком».
```
Нет БД — файла нет, и в `docs/.docs.json` нет ключа `migrations`.
Нет БД — файла нет, и в `.av-dev.toml` нет ключа `[docs] migrations`.
## `docs/security.md`
@@ -440,24 +440,32 @@ severity стоит здесь, а не выводится каждым прох
говорит (единственный дом, а не форма) — [canon.md](canon.md), раздел
`openspec/config.yaml`.
## `docs/.docs.json`
## `.av-dev.toml`
```json
{
"canon": <текущая версия>
}
```toml
# Раскладка av-dev в этом проекте: версия и настройки проверок.
version = <текущая версия>
[docs]
# migrations = "<путь>" — появится, когда появится БД
[tasks]
dir = "tasks"
```
`<текущая версия>` подставляет `init` или `adopt`, целым числом; берётся она из
`docs.py version` (строка «канон скрипта»), а не из памяти. Литерал здесь
протухает при каждом повышении канона, поэтому его тут и нет: незамещённый
плейсхолдер ломает разбор JSON громко, а отставшее число дало бы дрейф молча.
`docs.py version` (строка «раскладка скрипта»), а не из памяти. Литерал здесь
протухает при каждом повышении, поэтому его тут и нет: незамещённый плейсхолдер
ломает разбор TOML громко, а отставшее число дало бы дрейф молча.
Плюс `"migrations": "<путь>"`, если есть БД. Ключа `"tasks"` здесь **нет**:
настройки каталога задач и версия их формата переехали в свой файл `<каталог
задач>/.tasks.json`, потому что ведёт их другой плагин. Состав ключей —
[canon.md](canon.md).
Комментарии в файле — не украшение, а причина, по которой взят TOML: файл живёт
в чужом репозитории, и назначение числа читают из него самого. Скрипты это
учитывают и правят строку, а не переписывают файл. Состав ключей —
[canon.md](canon.md), раздел `.av-dev.toml`.
Имя файла — по плагину-владельцу, `av-dev-docs`. До версии 13 он звался
`.pm.json`, по распавшемуся `av-dev-pm`; проект с прежним именем `docs.py check`
называет отдельной строкой и зовёт переименовать.
Файл лежит **в корне репозитория**, а не в `docs/`: версия одна на всю
раскладку, и нужна она в том числе проекту, который канон документов ещё не
завёл. Прежние `docs/.docs.json` и `<каталог задач>/.tasks.json` остались от
трёх плагинов, слившихся в один; увидев их, `docs.py check` называет это прежней
раскладкой и зовёт `upgrade`.
+75 -52
View File
@@ -17,26 +17,47 @@
from __future__ import annotations
import argparse
import json
import importlib.util
import re
import subprocess
import sys
from dataclasses import dataclass, field
from pathlib import Path
from types import ModuleType
from typing import NoReturn
CANON_VERSION = 14
OK, DRIFT, USAGE, ENV, INTERNAL = 0, 1, 2, 3, 4
# Дом версии канона и путей, нужных проверкам. Имя — от плагина, который файл
# завёл: настройки канона документов ведёт `av-dev-docs`, и файл называется по
# нему. Прежнее имя досталось от `av-dev-pm` — плагина, который распался на
# четыре и которого больше нет; читать его скрипт не умеет намеренно, потому что
# два дома для версии канона расходятся молча, а переименование стоит одну
# команду и названо записью 13 журнала.
CONFIG = "docs/.docs.json"
LEGACY_CONFIG = "docs/.pm.json"
def _load_shared() -> ModuleType:
"""Общий читатель `.av-dev.toml` — `shared/config.py` этого же плагина.
Путь считается от файла скрипта, а не от рабочего каталога: скрипт зовут из
репозитория проекта, где ни плагина, ни его дерева в текущем каталоге нет.
Своё дерево — единственное, куда ходить можно; в чужое не ходим никогда.
"""
path = Path(__file__).resolve().parents[3] / "shared" / "config.py"
spec = importlib.util.spec_from_file_location("avdev_config", path)
if spec is None or spec.loader is None:
print(f"ОТКАЗ: не читается {path} — общий читатель настроек;"
f" переустанови плагин av-dev", file=sys.stderr)
sys.exit(ENV)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
conf = _load_shared()
# Версия раскладки одна на плагин и живёт в `shared/config.py`: её знают оба
# скрипта, и второе число здесь было бы вторым домом.
CANON_VERSION = conf.VERSION
# Дом версии и путей, нужных проверкам, — `.av-dev.toml` в корне репозитория.
# До слияния плагинов файлов было два, `docs/.docs.json` и `.tasks.json`, и
# версии двигались порознь; теперь дом один, и лежит он в корне, потому что
# настройки нужны и проекту без `docs/`.
CONFIG = conf.CONFIG_NAME
# --- Раскладка канона -------------------------------------------------------
@@ -77,7 +98,7 @@ CONDITIONAL_DOCS = {
# Обязательные файлы вне раскладки docs/.
REQUIRED = {
"CLAUDE.md": "памятка агенту: инварианты с severity, команды, семантика гейта",
CONFIG: "версия канона и пути, нужные проверкам",
CONFIG: "версия раскладки av-dev и пути, нужные проверкам",
}
# Файлы, которые документ-каталог обязан держать сверх README.md.
@@ -86,9 +107,10 @@ DOC_EXTRA = {
}
# Служебное в docs/ и каталог задач, оставшийся там от прежней раскладки. Формы
# у них скрипт не проверяет, и по разным причинам: `.docs.json` не markdown, а
# задачи **принадлежат другому плагину** — `av-dev-tasks`, со своим скриптом,
# своим конфигом и своей версией формата (её сторожит `tasks.py check`).
# у них скрипт не проверяет, и по разным причинам: `.docs.json` не markdown
# сам он теперь след прежней раскладки, о котором говорит `check_required`), а
# задачи ведёт **другой скилл** — `task-track`, со своим скриптом и своими
# проверками.
#
# Дом задач с версии 11 — `tasks/` в корне репозитория, то есть вне `docs/`
# вовсе. `docs/tasks/` здесь терпится потому, что непереехавший проект не должен
@@ -106,11 +128,11 @@ NOT_DOCS = {".docs.json", ".pm.json", "tasks"}
RETIRED = {
"review-brief.md": "документы канона и есть бриф; остаток — в review",
"review-journal.md": "→ документ review",
"plan.md": "→ tasks/ROADMAP.md (плагин av-dev-tasks)",
"plan.md": "→ tasks/ROADMAP.md (ведёт скилл task-track)",
"local-research.md": "→ документ research",
"specs": "поведение → openspec/specs/, обзор → тема architecture",
"drafts": "идея → запись research, отказ → ADR, порядок → ROADMAP.md",
"backlog": "→ tasks/ в корне репозитория (плагин av-dev-tasks)",
"backlog": "→ tasks/ в корне репозитория (ведёт скилл task-track)",
}
# --- Слаги в именах файлов --------------------------------------------------
@@ -264,16 +286,15 @@ def fail(code: int, msg: str) -> NoReturn:
def read_config(root: Path, rep: Report) -> dict:
path = root / CONFIG
if not path.exists():
return {}
"""Настройки проекта целиком; проверкам канона нужна секция `[docs]`."""
try:
data = json.loads(path.read_text(encoding="utf-8"))
except json.JSONDecodeError as exc:
fail(ENV, f"{CONFIG} не разбирается: {exc}")
if not isinstance(data, dict):
fail(ENV, f"{CONFIG} должен быть объектом")
return data
return conf.read(root)
except conf.ConfigError as exc:
fail(ENV, str(exc))
def docs_cfg(cfg: dict) -> dict:
return conf.section(cfg, "docs")
# --- Проверки ---------------------------------------------------------------
@@ -282,22 +303,19 @@ def read_config(root: Path, rep: Report) -> dict:
def check_version(root: Path, cfg: dict, rep: Report) -> None:
if not (root / CONFIG).exists():
return # об отсутствии файла скажет check_required, второй раз не нужно
if "canon" not in cfg:
rep.error(f"в {CONFIG} нет ключа canon — версия канона не объявлена")
return
got = cfg["canon"]
if not isinstance(got, int):
rep.error(f"canon в {CONFIG} должен быть целым числом, а не {got!r}")
got = conf.version(cfg)
if got is None:
rep.error(f"в {CONFIG} нет ключа version — версия раскладки не объявлена")
return
if got < CANON_VERSION:
rep.error(
f"проект приведён к канону версии {got}, текущая — {CANON_VERSION}: "
f"проект приведён к раскладке версии {got}, текущая — {CANON_VERSION}: "
f"нужен canon upgrade"
)
elif got > CANON_VERSION:
rep.error(
f"проект приведён к канону версии {got}, а скрипт знает {CANON_VERSION}: "
f"устарел плагин, обнови маркетплейс"
f"проект приведён к раскладке версии {got}, а скрипт знает"
f" {CANON_VERSION}: устарел плагин, обнови маркетплейс"
)
@@ -331,15 +349,17 @@ def check_required(root: Path, cfg: dict, rep: Report) -> None:
for rel, what in REQUIRED.items():
if (root / rel).exists():
continue
# Файл под прежним именем — это не «нет файла», а незаконченный переезд,
# и чинится он одной командой. Без этой ветки проект услышал бы «нет
# версии канона» и пошёл заводить второй файл рядом с первым.
if rel == CONFIG and (root / LEGACY_CONFIG).exists():
# Настройки под прежними именами — это не «нет файла», а незаконченный
# переезд. Без этой ветки проект слышал бы «нет версии» и шёл заводить
# второй файл рядом с первым, а старые остались бы вторым домом.
legacy = conf.legacy_files(root)
if rel == CONFIG and legacy:
rep.error(
f"нет {rel}{what}. Настройки лежат под прежним именем"
f" {LEGACY_CONFIG} (от плагина av-dev-pm, которого больше нет):"
f" `git mv {LEGACY_CONFIG} {rel}` — журнал канона, версия 13."
f" Прежнее имя не читается, поэтому в этом прогоне всё"
f"нет {rel}{what}. Настройки лежат по прежней раскладке"
f" ({', '.join(legacy)}) — она осталась от трёх плагинов, которые"
f" слились в один: перенеси значения и удали старые файлы"
f" операцией upgrade скилла av-dev:doc-canon (журнал, версия 1)."
f" Прежние имена не читаются, поэтому в этом прогоне всё"
f" остальное проверено так, будто настроек нет вовсе"
)
continue
@@ -360,18 +380,20 @@ def check_required(root: Path, cfg: dict, rep: Report) -> None:
if not (home / extra).is_file():
rep.error(f"нет docs/{name}/{extra}{why}")
docs = docs_cfg(cfg)
for name, (key, kind, what) in CONDITIONAL_DOCS.items():
home, complaint = doc_home(root, name)
if complaint:
rep.error(complaint)
if key in cfg and home is None:
if key in docs and home is None:
rep.error(
f"нет документа {name} (docs/{name}.md или docs/{name}/),"
f" категория «{kind}» — {what}"
f" (обязателен: в .docs.json объявлен {key})"
f" (обязателен: в {CONFIG} объявлен [docs] {key})"
)
elif key not in cfg and home is None:
rep.skip(f"{name} — в .docs.json нет ключа {key}, проверка неприменима")
elif key not in docs and home is None:
rep.skip(f"{name} — в {CONFIG} нет ключа [docs] {key},"
f" проверка неприменима")
def check_stray(root: Path, rep: Report) -> None:
@@ -551,9 +573,10 @@ def changed_files(root: Path, base: str, rep: Report) -> list[str] | None:
def check_migrations(root: Path, cfg: dict, base: str | None, rep: Report) -> None:
migrations = cfg.get("migrations")
migrations = docs_cfg(cfg).get("migrations")
if not migrations:
rep.skip("в .docs.json нет ключа migrations — сверка со схемой неприменима")
rep.skip(f"в {CONFIG} нет ключа [docs] migrations —"
f" сверка со схемой неприменима")
return
if not base:
rep.skip("база диффа не названа (--base) — сверка миграций со схемой не гонялась")
@@ -630,9 +653,9 @@ def cmd_check(args: argparse.Namespace) -> int:
def cmd_version(args: argparse.Namespace) -> int:
root = Path(args.dir).resolve()
cfg = read_config(root, Report())
got = cfg.get("canon", "не объявлена")
print(f"канон скрипта: {CANON_VERSION}")
print(f"канон проекта: {got}")
got = conf.version(cfg)
print(f"раскладка скрипта: {CANON_VERSION}")
print(f"раскладка проекта: {got if got is not None else 'не объявлена'}")
return OK