--- name: doc-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 ] # раскладка, ссылки, версия, сверки 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:task-track` (шаг 4, пункт 5). Каталоги `openspec/` и `tasks/` каноном не ведутся, и трогать их этому скиллу нечем, кроме вызова. **Копия.** Дом правила — `shared/plugin-boundary.md` в репозитории плагинов. Правится дом, а не этот файл. Плагины `av-dev` ставятся порознь, и ни один не вправе считать, что сосед на месте. **Чужой скилл зовётся полным именем** — `av-dev:doc-canon`, `av-dev:task-track`, `av-dev:code-review`. Короткое имя может разрешиться в устаревшую проектную копию из `.claude/skills/`, и подмены не будет видно ни в докладе, ни в поведении. **Путь в дерево чужого плагина не пишется никогда.** `$CLAUDE_PLUGIN_ROOT` ведёт только в свой плагин; вычисленный от него путь к соседу либо не откроется, либо откроет чужую установку. Нужен чужой справочник — зови владеющий им скилл, он прочитает его сам. **Вызов не разрешился — плагина в проекте нет.** Это исход, а не поломка: назови строкой доклада, чего теперь не делает никто, и продолжай работу. Молчать нельзя, пропуск неотличим от сделанного; выдумывать обходной путь нельзя тоже. **Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня установленных плагинов проект не ведёт — он разошёлся бы с действительностью молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.docs.json` — канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер. Имя файла — имя плагина, который его завёл, и держит он в том числе версию своего формата: у канона документов и у каталога задач они свои и двигаются порознь. Чем оборачивается отсутствие каждого — на самих пунктах шага 4. `adopt` из-за этого не останавливается ни в одном из двух случаев. ## `check` 1. `docs.py check`, при наличии базы диффа — с `--base`. 2. **Судей документов на каждом `check` не зови.** Ими владеет отдельный скилл — `av-dev:doc-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//spec.md` — **или уже там**, тогда файл дубль | | компоненты, транспорты, раскладка, деплой | `docs/architecture.md` | | конвенции чужой системы, формат чужих данных | `docs/research/` | | обоснование принятого решения | `docs/adr/` | **Дубль удаляется только после поимённой сверки**: открыть спеку capability, открыть файл, убедиться, что в файле нет ничего сверх спеки. Нашлось сверх — сперва переезжает в спеку дельтой, потом файл удаляется. ### 3. Покажи карту человеку `AskUserQuestion`, **не больше трёх вопросов за итерацию**, рекомендация первым вариантом. Показывается: сколько файлов, куда каждый, спорные отнесения, список «не разложилось». Механику (порядок строк, имена файлов внутри `research/`) не выноси — это не развилка. ### 4. Перенеси Порядок важен — он минимизирует окно, в котором ссылки битые: 1. `docs/.docs.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:task-track`**, сценарий адаптации: он владеет форматом задач. Он же переименует транслитные слаги в английские и тем же проходом починит перекрёстные ссылки; 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 скилл задач — его отчёт идёт в доклад отдельной строкой, и пункт «задачи без цели» в нём зелёным не станет: цели не сочиняются адаптацией (запрет записан у того, кто ведёт задачи), их проставляет человек порциями переоценки на первом груминге — скилл `av-dev:task-groom`. ### 5. Объяви переходное состояние Сразу после переноса канон **заполнен не весь**, и это нормально, но обязано быть названо, иначе следующий агент примет скелет за поломку. Печатается по факту: сколько документов стоят честной строкой вместо содержания, сколько маркеров долга в `architecture.md`, сколько задач без критериев приёмки. Закрывается порциями по ходу работы, а не одним заходом. ### 6. Позови обоих судей `docs.py` увидел раскладку, а не смысл: перенос растащил один факт по двум домам, оставил в `architecture.md` поведение, которому место в спеке, и оторвал ADR от его `design.md`. Ничего из этого скрипт не видит, и первый прогон на живом проекте обычно самый урожайный — правило единственного дома до адаптации никто не проверял. Вызови Skill **`av-dev:doc-healthcheck`** — он зовёт обоих судей на весь канон разом и держит разбор урожая порциями. **Передай им объявленное переходное состояние из шага 5** — иначе честная строка в незаполненном слоте вернётся находкой, а это не поломка, а объявленный долг. ### 7. Вычитай написанное — агент `doc-wording` Судьи смотрят утверждения, а `adopt` только что **писал текст**: честные строки в пустые слоты, переписанные при переносе абзацы, шапки перенесённых документов. Язык этого текста не проверяет никто другой, а зовущий здесь по определению тот, кто его и написал. Позови агента **по названной пачке** — документы, которые ты завёл или правил, плюс перенесённые целиком. Весь канон ему не нужен: он работает по списку, и список же служит ему словарём терминов. Находки — готовые формулировки, подставляешь их ты. ## `upgrade` — канон вырос 1. `docs.py version` — версия проекта и версия скрипта. 2. Проект новее скрипта — **обнови маркетплейс**, а не проект: это отстал плагин. 3. Иначе иди по [changelog.md](references/changelog.md) снизу вверх от версии проекта до текущей и делай названное в каждой записи. Записи независимы и применяются по порядку. 4. Подними `canon` в `docs/.docs.json` до текущей. 5. `docs.py check`. 6. **Позови судей** — Skill `av-dev:doc-healthcheck`. 7. **Позови вычитку** — агент `doc-wording`, но **только по тем документам, которых записи журнала коснулись**, и только если правка была текстовой, а не переименованием файла. Записи журнала пишутся руками в проектной прозе, и дописанный по журналу раздел — такой же свежий текст, как на синке. Записи журнала описывают **что сделать проекту**. Если запись этого не говорит — это дефект журнала, и о нём надо сказать, а не догадываться. **Каталог задач повышается своим журналом, а не этим.** У него своя версия формата — ключ `tasks` в `<каталог задач>/.tasks.json`, — и двигает её плагин `av-dev-tasks`. Запись канона вправе сказать «позови соседа», но не вправе двигать чужое число: две версии, которые ходят по одному журналу, разъезжаются на первом же проекте, поставившем один плагин без другого. Отстал каталог задач — это скажет `tasks.py check` своей строкой гейта, а повысит скилл `av-dev:task-track`. **Шаг 6 обязателен, и вот почему.** `check` сверяет **число** в `.docs.json` с версией скрипта — и только его. Применена ли запись журнала **по существу**, он не знает: проект несёт `"canon": 6` и может не иметь того, чего требовала любая из пройденных версий. Записи применяются руками (переименовать секцию, проставить типы, дописать раздел каждому `fix`), а ручной проход по нескольким записям подряд — ровно то место, где половина шага делается и забывается. Судьи и есть проверка, которой у `upgrade` иначе нет: `doc-consistency` увидит, что документы разошлись после переименований, `doc-code-drift` — что переехавший факт разошёлся с кодом. ## Чего этот скилл не делает - **Не сочиняет содержание.** Пустой слот получает честную строку о том, что его наполнить пока нечем, а не выдуманный абзац. Придуманный периметр модели угроз хуже отсутствующего: по нему будут строиться находки. - **Не удаляет то, чьё содержимое не нашло дом.** Оригинал живёт, пока не названо поимённо, куда переехал каждый его кусок. - **Не ведёт содержимое канона** — это скилл `docs`. Здесь только раскладка. - **Не заводит проект с нуля** — это скилл `init`. - **Не правит историю.** В старых коммитах старые пути остаются, и это нормально. ## Доклад - Что нашёл `docs.py`: код выхода и число пунктов дрейфа. - Что перенесено: файл → дом, числом и поимённо для спорного. - **Удалённые дубли** — с указанием, против какой спеки сверялся каждый. - **Не разложилось** — поимённо, с причиной. - Переходное состояние числами: честных строк, маркеров долга, задач без критериев. - **Граница покрытия**: что проверила машина, что судил ты, чего не смотрел никто.