av-dev-pm расколот на av-dev-docs и av-dev-tasks

Плагин владел двумя разными вещами сразу — документацией проекта и учётом работ,
— и это мешало обеим. Канон нельзя было поставить без задач, задачи без канона, а
язык проектных текстов лежал внутри скилла canon и потому принадлежал половине.
Теперь плагина два, каждый ставится сам по себе.

av-dev-docs: скиллы canon, docs, init; агенты doc-consistency, doc-code-drift,
doc-wording; скрипт docs.py. av-dev-tasks: скиллы tasks, session; агенты
task-form, task-wording; скрипт tasks.py.

Между собой они зовутся через пространство имён, а не по пути в чужое дерево.
Все относительные ссылки, пересекшие границу плагина, сняты: tasks больше не
указывает в canon, canon не указывает в tasks. Вместо ссылки — имя скилла и
оговорка, что вызов может не разрешиться, и это исход, а не поломка.

То, что нужно обоим дословно, стало вторым общим домом. Словарь «Сопровождение и
эксплуатация» назван в трёх местах трёх плагинов — секция роадмапа, раздел
«Эксплуатация» в architecture.md, тема ревью operations — и ни один из трёх им не
владеет; он уехал в shared/operations.md, а canon.md и скилл задач везут копии.
Три перечня «чем держат проект» уже разъезжались на «метриках и логах» против
«мониторинга», так что ссылка тут не годится: плагин, поставленный в одиночку,
получил бы указатель в никуда. Тем же способом язык: у av-dev-tasks появилась
своя копия language.md.

Копий стало 18 при 8 домах.

Переименования разведены по смыслу, а не заменой строки: где речь о каноне —
av-dev-docs, где об учёте задач — av-dev-tasks. В пайплайне таких мест
одиннадцать, и оба адресата там встречаются вперемешку.

Журналы (DECISIONS, TODO, HISTORY) намеренно не тронуты: они описывают состояние
на момент записи. По той же причине оставлена наблюдённая строка в комментарии
docs.py — она цитирует конфиг живого проекта, а не называет плагин.

Не входит в этот заход и названо отдельно: слияние canon и docs в один скилл,
разделение docs/.pm.json на два конфига и переезд openspec в пайплайн.

Гейт зелёный: копии, фронтматтеры, диаграммы, json. Оба скрипта прогнаны после
переезда — docs.py version и tasks.py check на фикстуре.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
av
2026-08-09 14:06:26 +03:00
co-authored by Claude Opus 5
parent 86e22d932c
commit 00ddfb0dde
42 changed files with 417 additions and 97 deletions
+249
View File
@@ -0,0 +1,249 @@
---
name: canon
description: Привести проект к канону документов av-dev и держать его в соответствии — три операции одной машиной сравнения. check — что разошлось с текущей версией канона; adopt — перевод проекта из любой прежней раскладки (docs/specs, drafts, backlog, BRIEF.md, review-brief) в канон с переносом файлов; upgrade — повышение проекта с версии канона N до текущей по журналу версий. Использовать, когда просят проверить документацию проекта, перевести проект на канон, обновить его под новую версию канона или когда пришли в старый проект и надо понять, что в нём не так. Заведение нового проекта с нуля — скилл init.
---
# Приведение проекта к канону
Три операции, одна машина сравнения с разными исходами:
| Операция | Когда | Исход |
| --- | --- | --- |
| `check` | начало сессии, шаг синка, гейт | что разошлось |
| `adopt` | проект в чужой раскладке | перенос в канон |
| `upgrade` | канон вырос, проект отстал | по журналу версий |
**Определение канона — [references/canon.md](references/canon.md).** Здесь оно не
пересказывается: два описания одной раскладки разъедутся, и работать будет то,
которое прочитали последним. Прочитай его **до** первой правки.
- [references/skeletons.md](references/skeletons.md) — **что именно класть** в
каждый незаполненный слот. Не выдумывай заглушку своей формы: `docs.py`
узнаёт только плейсхолдер `<!-- заполнить: … -->` из шаблонов.
- [references/language.md](references/language.md) — **как это написано словами**:
информационный стиль, применённый к проектным текстам, таблицы англицизмов и
жаргона. Раскладка отвечает, где текст лежит, — этот файл отвечает, каким он
должен быть. Правила общие для документов канона, задач, решений ADR и
записок разведки, и дом у них общий — `shared/language.md` в репозитории
плагинов, а этот файл его копия. Вычитывают их два прохода по охвату:
документы — `doc-wording`, записи каталога задач — `task-wording`.
- [references/changelog.md](references/changelog.md) — журнал версий канона.
## Три правила, из которых всё следует
1. **Сперва карта, потом файлы.** Человеку показывается, что найдено, как
разложилось и **что не разложилось**, — и только после подтверждения
переносится хоть один файл. Массовый перенос без подтверждения разгребать
дороже, чем согласовать.
2. **Ничего не терять.** Содержимое переезжает целиком; ссылки чинятся тем же
проходом, что и перенос. Старый файл удаляется **только** после того, как
всё его содержимое нашло дом, и это названо поимённо.
3. **Что не классифицировалось — назвать.** Проглоченный абзац выглядит как
«всё перенеслось». Список «не разложилось» идёт в доклад целиком, с причиной
по каждому пункту.
## Инструмент
```
ds="$CLAUDE_PLUGIN_ROOT/skills/canon/scripts/docs.py"
python3 $ds check --dir <корень> [--base <rev>] # раскладка, ссылки, версия, сверки
python3 $ds version --dir <корень> # версия канона скрипта и проекта
python3 $ds openspec-form # форма config.yaml против живого OpenSpec
```
**`openspec-form` зовут не на каждом прогоне, а когда о нём попросил `check`.**
Форма `openspec/config.yaml` описана в каноне слепком чужого инструмента — имя
схемы и перечень артефактов, — и слепок стареет молча: OpenSpec переименует
артефакт, правила под старым именем перестанут применяться, а конфиг останется
выглядеть написанным. Поэтому `check` каждым прогоном сравнивает `major.minor`
установленного OpenSpec с тем, на котором форма сверялась, и при расхождении
даёт замечание с этой командой. Команда ничего не правит: она спрашивает
инструмент и печатает, что разошлось. **Чинится это в плагине, а не в проекте**
константы `docs.py`, скелет в `skeletons.md` и запись в журнал версий канона.
**Коды выхода — тот же словарь, что у `tasks.py`:** 0 сошлось, 1 дрейф, 2 ошибка
употребления, 3 окружение, 4 внутренний сбой. Ветвись на коде, а не на тексте.
Различать 1 и 3 обязательно: «дрейф раскладки» — рабочая ситуация, «это не
корень проекта» — нерабочая.
### Граница механизируемого — объявляется вслух
Скрипт печатает её сам последним абзацем, и **эту строку из доклада выбрасывать
нельзя**. `check`, отчитавшийся «канон соблюдён» на проекте, где из шести файлов
три лишние, хуже отсутствующего.
Машина **дрейфом** считает: отсутствующий путь канона, файл вне канона, битую
ссылку, отставшую версию, capability без упоминания в обзоре, миграцию без правки
`database.md`. **Замечанием** — незаполненный плейсхолдер и слабое упоминание
capability: незаполненный канон это переходное состояние, а не отказ. Маркеры
долга просто считает числом.
Того, чего она не умеет, **ты не судишь сам** — для этого есть два агента, и
разведены они по глубине:
| Агент | Что смотрит | Читает |
| --- | --- | --- |
| `doc-consistency` | смысловой дубль, прямое противоречие между документами, поведение в `architecture.md` вместо спек, ADR без ссылки и парного статуса, число без провенанса, заглушка вместо честной строки | `docs/`, `openspec/` |
| `doc-code-drift` | протухший факт: имя ветки, команды, пути, зависимости поимённо, настройки с числом, единые точки проекта, capability | весь репозиторий |
Судит **не тот, кто писал**: самопроверка документа слабее всего ровно там, где
формулировка казалась удачной при написании. Ни один из них ничего не правит —
оба возвращают готовые формулировки, подставляешь ты.
## `check`
1. `docs.py check`, при наличии базы диффа — с `--base`.
2. **Агентов на каждом `check` не зови.** Оба — `doc-consistency` и
`doc-code-drift` — зовутся раз в спринт (шаг сессии), а также шагом 6 `adopt`
и шагом 6 `upgrade`, на весь канон разом. Они дороги: `doc-consistency` — тем,
что на `opus` (сличение утверждений это суждение), `doc-code-drift` — тем, что
читает репозиторий целиком, хотя сам идёт на `sonnet`. Позвал
`doc-code-drift` — передай ему раздел запретов `CLAUDE.md`.
3. Доклад: вывод скрипта строкой исхода, находки агентов поимённо, **граница
покрытия** — что смотрели и чего не смотрели, и **кого из двоих позвал**:
доклад, умолчавший об этом, читается как «сверено».
Дрейф раскладки чинится переносом; смысловые находки — это либо правка
документа, либо задача, если работы больше чем на абзац.
## `adopt` — проект в чужой раскладке
### 1. Осмотрись
`docs.py check` — он уже назовёт упразднённые слоты с адресом, куда каждый
уезжает. **Но смотрит он только верхний уровень `docs/`:** упразднённое в корне
репозитория (`BRIEF.md`) и во вложенных каталогах он не назовёт никогда, поэтому
корневые `*.md` читай глазами. Плюс: `CLAUDE.md`, `openspec/specs/` (список
capability), `openspec/config.yaml`.
### 2. Составь карту
Каждый найденный файл получает строку: **куда едет, целиком или разбирается, что
делать с оригиналом**. Разбор `docs/specs/` — самое дорогое место, и он делается
поимённо по capability:
| Что в файле | Куда |
| --- | --- |
| требования, сценарии, поведение | `openspec/specs/<capability>/spec.md`**или уже там**, тогда файл дубль |
| компоненты, транспорты, раскладка, деплой | `docs/architecture.md` |
| конвенции чужой системы, формат чужих данных | `docs/research/` |
| обоснование принятого решения | `docs/adr/` |
**Дубль удаляется только после поимённой сверки**: открыть спеку capability,
открыть файл, убедиться, что в файле нет ничего сверх спеки. Нашлось сверх —
сперва переезжает в спеку дельтой, потом файл удаляется.
### 3. Покажи карту человеку
`AskUserQuestion`, **не больше трёх вопросов за итерацию**, рекомендация первым
вариантом. Показывается: сколько файлов, куда каждый, спорные отнесения, список
«не разложилось». Механику (порядок строк, имена файлов внутри `research/`) не
выноси — это не развилка.
### 4. Перенеси
Порядок важен — он минимизирует окно, в котором ссылки битые:
1. `docs/.pm.json` с `{"canon": <текущая версия>}` и путём миграций, если БД есть;
2. каталоги канона и скелет **по [references/skeletons.md](references/skeletons.md)**:
незаполненное — одной честной информативной строкой, а не «TBD»;
3. **OpenSpec, если его нет**`openspec init --tools claude`, и `config.yaml`
по тому же скелету. Каталог есть, а `config.yaml` из коробки — тот же случай,
что отсутствие: закомментированный пример выглядит настройкой и не является
ею. Пересказ инвариантов, конвенций и правил ревью из `context` вычисти
ссылкой на дом — на переводимом проекте он там почти наверняка есть;
4. переносы содержимого;
5. каталог задач — **вызови скилл `av-dev-tasks:tasks`**, сценарий адаптации: он
владеет форматом задач. Он же переименует транслитные слаги в английские и
тем же проходом починит перекрёстные ссылки;
6. починка ссылок на перенесённое во всём репозитории — `docs/`, `openspec/`,
`CLAUDE.md`, `README.md`;
7. удаление оригиналов — **только тех, чьё содержимое найдено в новом доме**;
8. **шаг `docs.py check` в гейт проекта.** Путь к скрипту — переменной с
умолчанием на канонический путь маркетплейса, чтобы переустановка плагина не
меняла `Taskfile`; шаг обязан **краснеть внятно**, если скрипт не найден, а не
пропускаться. Передай ему базу диффа (`--base`) той же переменной, что и
остальным шагам гейта: без неё сверка миграций со схемой не гоняется вовсе.
Пример строки покажи человеку — гейт принадлежит проекту, и правит его он;
9. `docs.py check` — до **отсутствия дрейфа раскладки**. Замечания
(незаполненные плейсхолдеры, слабое упоминание capability) остаются:
незаполненный канон это объявленное переходное состояние из шага 5, а не
отказ. **Пункт «задачи без цели» из вложенной проверки `tasks.py` тоже
остаётся** и зелёным на этом шаге не станет: цели не сочиняются адаптацией
(запрет записан у того, кто ведёт задачи, — сценарий адаптации скилла
`av-dev-tasks:tasks`), их
проставляет человек порциями переоценки на первой сессии. Пересчитай эти
пункты в докладе переходного состояния — не выдавай их за поломку и не
молчи о них.
### 5. Объяви переходное состояние
Сразу после переноса канон **заполнен не весь**, и это нормально, но обязано
быть названо, иначе следующий агент примет скелет за поломку.
Печатается по факту: сколько документов стоят честной строкой вместо
содержания, сколько маркеров долга в `architecture.md`, сколько задач без
критериев приёмки. Закрывается порциями по ходу работы, а не одним заходом.
### 6. Позови обоих судей
`docs.py` увидел раскладку, а не смысл: перенос растащил один факт по двум домам,
оставил в `architecture.md` поведение, которому место в спеке, и оторвал ADR от
его `design.md`. Ничего из этого скрипт не видит, и первый прогон на живом
проекте обычно самый урожайный — правило единственного дома до адаптации никто не
проверял.
Зови **`doc-consistency`** (документы между собой и с openspec) и
**`doc-code-drift`** (факты против кода). Разбирай порциями, а не одним заходом.
**Передай им объявленное переходное состояние из шага 5** — иначе честная строка
в незаполненном слоте вернётся находкой, а это не поломка, а объявленный долг.
## `upgrade` — канон вырос
1. `docs.py version` — версия проекта и версия скрипта.
2. Проект новее скрипта — **обнови маркетплейс**, а не проект: это отстал
плагин.
3. Иначе иди по [changelog.md](references/changelog.md) снизу вверх от версии
проекта до текущей и делай названное в каждой записи. Записи независимы и
применяются по порядку.
4. Подними `canon` в `docs/.pm.json` до текущей.
5. `docs.py check`.
6. **Позови обоих судей**`doc-consistency` и `doc-code-drift`.
Записи журнала описывают **что сделать проекту**. Если запись этого не говорит —
это дефект журнала, и о нём надо сказать, а не догадываться.
**Шаг 6 обязателен, и вот почему.** `check` сверяет **число** в `.pm.json` с
версией скрипта — и только его. Применена ли запись журнала **по существу**, он
не знает: проект несёт `"canon": 6` и может не иметь того, чего требовала любая
из пройденных версий. Записи применяются руками (переименовать секцию, проставить
типы, дописать раздел каждому `fix`), а ручной проход по нескольким записям
подряд — ровно то место, где половина шага делается и забывается. Судьи и есть
проверка, которой у `upgrade` иначе нет: `doc-consistency` увидит, что документы
разошлись после переименований, `doc-code-drift` — что переехавший факт
разошёлся с кодом.
## Чего этот скилл не делает
- **Не сочиняет содержание.** Пустой слот получает честную строку о том, что его
наполнить пока нечем, а не выдуманный абзац. Придуманный периметр модели угроз
хуже отсутствующего: по нему будут строиться находки.
- **Не удаляет то, чьё содержимое не нашло дом.** Оригинал живёт, пока не
названо поимённо, куда переехал каждый его кусок.
- **Не ведёт содержимое канона** — это скилл `docs`. Здесь только раскладка.
- **Не заводит проект с нуля** — это скилл `init`.
- **Не правит историю.** В старых коммитах старые пути остаются, и это нормально.
## Доклад
- Что нашёл `docs.py`: код выхода и число пунктов дрейфа.
- Что перенесено: файл → дом, числом и поимённо для спорного.
- **Удалённые дубли** — с указанием, против какой спеки сверялся каждый.
- **Не разложилось** — поимённо, с причиной.
- Переходное состояние числами: честных строк, маркеров долга, задач без
критериев.
- **Граница покрытия**: что проверила машина, что судил ты, чего не смотрел
никто.