Files
dev-skills/av-dev-docs/skills/canon/SKILL.md
T
avandClaude Opus 5 c1890d9e71 скелеты канона догнали канон 12: спринт, версия, чужая проверка
Аудит четырьмя сабагентами показал систематическую дыру: механика правилась,
а описывающее её вовне — нет. Дороже всего скелеты: они уезжают в проект.

- слоты спринта в скелете CLAUDE.md стали слотами груминга, имена взяты
  у groom, а не выдуманы заново — журнал версии 12 их уже назвал
- "canon": 11 в двух образцах стал плейсхолдером: литерал протухал третий
  раз подряд, а незамещённый плейсхолдер ломает разбор громко
- обещание, что docs.py проверяет форму openspec/config.yaml, снято из трёх
  мест; владелец назван полным именем, с оговоркой об отсутствии плагина
- adopt ставил в гейт проекта один шаг из трёх; соседские ставятся по следу
  присутствия, следа нет — строка доклада
- битая ссылка на tasks/ROADMAP.md из скелета паспорта

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 17:40:14 +03:00

293 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 <корень> # версия канона скрипта и проекта
```
**Формы `openspec/config.yaml` здесь больше нет.** Каталог принадлежит конвейеру,
и форму смотрит его скрипт — `av-dev-code`, скилл `openspec`, команда
`openspec.py check`. Проект работает по OpenSpec, а плагина конвейера нет — форму
не проверяет никто, и это надо сказать строкой доклада, а не считать, что она
верна.
**Коды выхода — тот же словарь, что у `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 | весь репозиторий |
Судит **не тот, кто писал**: самопроверка документа слабее всего ровно там, где
формулировка казалась удачной при написании. Ни один из них ничего не правит —
оба возвращают готовые формулировки, подставляешь ты.
## Обращение к соседним плагинам
`adopt` зовёт двоих: `av-dev-code:openspec` (шаг 4, пункт 3) и
`av-dev-tasks:tasks` (шаг 4, пункт 5). Каталоги `openspec/` и `tasks/` каноном не
ведутся, и трогать их этому скиллу нечем, кроме вызова.
**Копия.** Дом правила — `shared/plugin-boundary.md` в репозитории плагинов.
Правится дом, а не этот файл.
<!-- копия: граница-плагинов из shared/plugin-boundary.md -->
Плагины `av-dev` ставятся порознь, и ни один не вправе считать, что сосед на
месте.
**Чужой скилл зовётся полным именем**`av-dev-docs:canon`, `av-dev-tasks:tasks`,
`av-dev-code:review`. Короткое имя может разрешиться в устаревшую проектную копию
из `.claude/skills/`, и подмены не будет видно ни в докладе, ни в поведении.
**Путь в дерево чужого плагина не пишется никогда.** `$CLAUDE_PLUGIN_ROOT` ведёт
только в свой плагин; вычисленный от него путь к соседу либо не откроется, либо
откроет чужую установку. Нужен чужой справочник — зови владеющий им скилл, он
прочитает его сам.
**Вызов не разрешился — плагина в проекте нет.** Это исход, а не поломка: назови
строкой доклада, чего теперь не делает никто, и продолжай работу. Молчать нельзя,
пропуск неотличим от сделанного; выдумывать обходной путь нельзя тоже.
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.pm.json`
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
<!-- /копия: граница-плагинов -->
Чем оборачивается отсутствие каждого — на самих пунктах шага 4. `adopt` из-за
этого не останавливается ни в одном из двух случаев.
## `check`
1. `docs.py check`, при наличии базы диффа — с `--base`.
2. **Судей документов на каждом `check` не зови.** Ими владеет отдельный скилл —
`av-dev-docs:healthcheck`, — и там же записано, когда его звать: он дорог, и
прогон по каждому `check` не окупается. `check` отвечает на «сходится ли
форма», `healthcheck` — на «не разошлись ли утверждения».
3. Доклад: вывод скрипта строкой исхода и **граница покрытия** — что смотрели и
чего не смотрели. Если суждение здесь нужно, скажи это строкой и предложи
`healthcheck`, а не зови агентов сам.
Дрейф раскладки чинится переносом; смысловые находки — это либо правка
документа, либо задача, если работы больше чем на абзац.
## `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, если его нет или `config.yaml` остался примером** — **вызови
Skill `av-dev-code:openspec`**. Каталог принадлежит конвейеру, и команда
заведения с формой файла живут там. Пересказ инвариантов, конвенций и правил
ревью из `context` вычисти ссылкой на дом — на переводимом проекте он там
почти наверняка есть. Вызов не разрешился — `docs.py` о каталоге тогда тоже
молчит, и форму `config.yaml` не проверяет никто; скажи это строкой;
4. переносы содержимого;
5. каталог задач — **вызови скилл `av-dev-tasks:tasks`**, сценарий адаптации: он
владеет форматом задач. Он же переименует транслитные слаги в английские и
тем же проходом починит перекрёстные ссылки;
6. починка ссылок на перенесённое во всём репозитории — `docs/`, `openspec/`,
`CLAUDE.md`, `README.md`;
7. удаление оригиналов — **только тех, чьё содержимое найдено в новом доме**;
8. **шаг `docs.py check` в гейт проекта.** Путь к скрипту — переменной с
умолчанием на канонический путь маркетплейса, чтобы переустановка плагина не
меняла `Taskfile`; шаг обязан **краснеть внятно**, если скрипт не найден, а не
пропускаться. Передай ему базу диффа (`--base`) той же переменной, что и
остальным шагам гейта: без неё сверка миграций со схемой не гоняется вовсе.
Пример строки покажи человеку — гейт принадлежит проекту, и правит его он.
**Шагов в гейте три, и они независимы.** `docs.py check` не тянет за собой
ни задачи, ни конвейер: без своих строк дрейф каталога задач и формы
`openspec/config.yaml` перестаёт ловиться совсем. Ставь соседские шаги по
следу присутствия — `<каталог задач>/.tasks.json` есть, значит ставится
`tasks.py check --dir <каталог задач>`; `openspec/config.yaml` есть, значит
ставится `openspec.py check`. Следа нет — плагина в проекте нет, шаг не
ставится, и это **строка доклада**, а не поломка: назови, чего теперь не
проверяет никто. У каждого шага своя переменная пути с тем же умолчанием на
канонический путь маркетплейса; `$CLAUDE_PLUGIN_ROOT` в гейт не подставляй —
он ведёт только в свой плагин;
9. `docs.py check` — до **отсутствия дрейфа раскладки**. Замечания
(незаполненные плейсхолдеры, слабое упоминание capability) остаются:
незаполненный канон это объявленное переходное состояние из шага 5, а не
отказ. Пересчитай эти пункты в докладе переходного состояния — не выдавай
их за поломку и не молчи о них.
**Задачи `docs.py` не проверяет** — их ведёт другой плагин, и согласованность
каталога показывает только `tasks.py check`. Позвал на шаге 5 скилл задач —
его отчёт идёт в доклад отдельной строкой, и пункт «задачи без цели» в нём
зелёным не станет: цели не сочиняются адаптацией (запрет записан у того, кто
ведёт задачи), их проставляет человек порциями переоценки на первой сессии.
### 5. Объяви переходное состояние
Сразу после переноса канон **заполнен не весь**, и это нормально, но обязано
быть названо, иначе следующий агент примет скелет за поломку.
Печатается по факту: сколько документов стоят честной строкой вместо
содержания, сколько маркеров долга в `architecture.md`, сколько задач без
критериев приёмки. Закрывается порциями по ходу работы, а не одним заходом.
### 6. Позови обоих судей
`docs.py` увидел раскладку, а не смысл: перенос растащил один факт по двум домам,
оставил в `architecture.md` поведение, которому место в спеке, и оторвал ADR от
его `design.md`. Ничего из этого скрипт не видит, и первый прогон на живом
проекте обычно самый урожайный — правило единственного дома до адаптации никто не
проверял.
Вызови Skill **`av-dev-docs:healthcheck`** — он зовёт обоих судей на весь канон
разом и держит разбор урожая порциями.
**Передай им объявленное переходное состояние из шага 5** — иначе честная строка
в незаполненном слоте вернётся находкой, а это не поломка, а объявленный долг.
## `upgrade` — канон вырос
1. `docs.py version` — версия проекта и версия скрипта.
2. Проект новее скрипта — **обнови маркетплейс**, а не проект: это отстал
плагин.
3. Иначе иди по [changelog.md](references/changelog.md) снизу вверх от версии
проекта до текущей и делай названное в каждой записи. Записи независимы и
применяются по порядку.
4. Подними `canon` в `docs/.pm.json` до текущей.
5. `docs.py check`.
6. **Позови судей** — Skill `av-dev-docs:healthcheck`.
Записи журнала описывают **что сделать проекту**. Если запись этого не говорит —
это дефект журнала, и о нём надо сказать, а не догадываться.
**Шаг 6 обязателен, и вот почему.** `check` сверяет **число** в `.pm.json` с
версией скрипта — и только его. Применена ли запись журнала **по существу**, он
не знает: проект несёт `"canon": 6` и может не иметь того, чего требовала любая
из пройденных версий. Записи применяются руками (переименовать секцию, проставить
типы, дописать раздел каждому `fix`), а ручной проход по нескольким записям
подряд — ровно то место, где половина шага делается и забывается. Судьи и есть
проверка, которой у `upgrade` иначе нет: `doc-consistency` увидит, что документы
разошлись после переименований, `doc-code-drift` — что переехавший факт
разошёлся с кодом.
## Чего этот скилл не делает
- **Не сочиняет содержание.** Пустой слот получает честную строку о том, что его
наполнить пока нечем, а не выдуманный абзац. Придуманный периметр модели угроз
хуже отсутствующего: по нему будут строиться находки.
- **Не удаляет то, чьё содержимое не нашло дом.** Оригинал живёт, пока не
названо поимённо, куда переехал каждый его кусок.
- **Не ведёт содержимое канона** — это скилл `docs`. Здесь только раскладка.
- **Не заводит проект с нуля** — это скилл `init`.
- **Не правит историю.** В старых коммитах старые пути остаются, и это нормально.
## Доклад
- Что нашёл `docs.py`: код выхода и число пунктов дрейфа.
- Что перенесено: файл → дом, числом и поимённо для спорного.
- **Удалённые дубли** — с указанием, против какой спеки сверялся каждый.
- **Не разложилось** — поимённо, с причиной.
- Переходное состояние числами: честных строк, маркеров долга, задач без
критериев.
- **Граница покрытия**: что проверила машина, что судил ты, чего не смотрел
никто.