Оба пункта заметок оказались одним классом: правило записано и никем не исполняется. Слаги. canon.md говорил «слаги файлов, capability и задач — английские, kebab-case» одной строкой в хвосте раскладки, а docs.py имён файлов не смотрел вовсе. Итог нашёлся в самом плагине: единственный пример ADR в скилле docs назывался ADR-2026-08-03-ochered-tablicej. Раскладка канона при этом приглашала к нарушению — в схеме стояли плейсхолдеры <тема>.md, то есть слово «тема» по-русски там, где надо писать <slug>. docs.py check теперь смотрит имена: кириллица и не-kebab-case жёстко, форма ADR-ГГГГ-ММ-ДД-slug.md жёстко, транслит эвристикой, то есть замечанием. Проверяются docs/conventions, docs/research, docs/adr и имена capability; каталог задач не трогается — его слаги ведёт tasks.py. Набор маркеров транслита подобран так, чтобы ложных срабатываний не было вовсе: выброшены ost (ловит post, cost), sch (schema), ya (yaml), nost (nostalgia), хвост ii (radii). Цена названа в комментарии — sostoyanie-partii проходит мимо. Правило, краснеющее на правде, приучает пролистывать весь блок, и это дороже пропуска. Агенты. В canon.md есть таблица «Что проверяет машина, а что человек», и её правая колонка — смысловой дубль, поведение в architecture.md, протухший факт, достаточность честной строки — три версии описывала работу, которую никто не делал: скилл canon предлагал агенту судить об этом самому, то есть проверять то, что он же и писал. Заведены двое, разрез по глубине — тот же довод, что развёл task-form и doc-wording. doc-consistency читает docs/ и openspec/, сверяет документы между собой (факт в двух домах, прямое противоречие, поведение в обзоре вместо спек, ADR без ссылки на design.md и без парного статуса, число без провенанса, заглушка вместо честной строки) и зовётся на шаге синка документации. doc-code-drift читает репозиторий, отвечает на «этот факт ещё верен» и зовётся раз в спринт на сессии. Перечень фактов, сверяемых с кодом, закрыт: имя ветки, команды, пути, зависимости поимённо, настройки с числом, единые точки проекта, capability, проверяемые инварианты. «Сверить архитектуру с кодом» — задача без дна, и агент, которому её поставили, выдаёт правдоподобную труху. Отсюда форма его доклада: начинается таблицей проверенного, а не находками, — по ней видно, чего он не смотрел. Карта домов уехала в устав doc-consistency помеченной копией: устав ссылался на файл плагина, а агент работает в репозитории проекта, где плагина может не быть. copies.py её сторожит. Попутно: докстрока copies.py показывала закрывающие маркеры как <!-- /дом -->, а код требует <!-- /дом: <id> -->. Нашлось первой же попыткой ими воспользоваться. DECISIONS тема 28 (ННОО–ХХЦЦ, следствия 105–108). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
205 lines
17 KiB
Markdown
205 lines
17 KiB
Markdown
---
|
||
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 и
|
||
записок разведки; вычитывает их отдельным проходом агент `doc-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 <корень> # версия канона скрипта и проекта
|
||
```
|
||
|
||
**Коды выхода — тот же словарь, что у `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. **Позови `doc-consistency`** на документы, которых касалась работа. Не «заодно
|
||
по всему `docs/`»: агент зовётся пачкой, но пачка отбирается работой.
|
||
3. **`doc-code-drift`** — не на каждом `check`, а перед приведением проекта к
|
||
канону и раз в спринт (шаг сессии). Он дорог: читает репозиторий и гоняет
|
||
команды. Позвал — передай ему раздел запретов `CLAUDE.md`.
|
||
4. Доклад: вывод скрипта строкой исхода, находки агентов поимённо, **граница
|
||
покрытия** — что смотрели и чего не смотрели, и **был ли позван
|
||
`doc-code-drift`**: доклад, умолчавший об этом, читается как «с кодом сверено».
|
||
|
||
Дрейф раскладки чинится переносом; смысловые находки — это либо правка
|
||
документа, либо задача, если работы больше чем на абзац.
|
||
|
||
## `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. переносы содержимого;
|
||
4. каталог задач — **вызови скилл `av-dev-pm:tasks`**, сценарий адаптации: он
|
||
владеет форматом задач, включая переименование транслитных слагов в
|
||
английские вместе с починкой перекрёстных ссылок;
|
||
5. починка ссылок на перенесённое во всём репозитории — `docs/`, `openspec/`,
|
||
`CLAUDE.md`, `README.md`;
|
||
6. удаление оригиналов — **только тех, чьё содержимое найдено в новом доме**;
|
||
7. **шаг `docs.py check` в гейт проекта.** Путь к скрипту — переменной с
|
||
умолчанием на канонический путь маркетплейса, чтобы переустановка плагина не
|
||
меняла `Taskfile`; шаг обязан **краснеть внятно**, если скрипт не найден, а не
|
||
пропускаться. Передай ему базу диффа (`--base`) той же переменной, что и
|
||
остальным шагам гейта: без неё сверка миграций со схемой не гоняется вовсе.
|
||
Пример строки покажи человеку — гейт принадлежит проекту, и правит его он;
|
||
8. `docs.py check` — до **отсутствия дрейфа раскладки**. Замечания
|
||
(незаполненные плейсхолдеры, слабое упоминание capability) остаются:
|
||
незаполненный канон это объявленное переходное состояние из шага 5, а не
|
||
отказ. **Пункт «задачи без цели» из вложенной проверки `tasks.py` тоже
|
||
остаётся** и зелёным на этом шаге не станет: цели не сочиняются адаптацией
|
||
(запрет в [tasks/references/adopt.md](../tasks/references/adopt.md)), их
|
||
проставляет человек порциями переоценки на первой сессии. Пересчитай эти
|
||
пункты в докладе переходного состояния — не выдавай их за поломку и не
|
||
молчи о них.
|
||
|
||
### 5. Объяви переходное состояние
|
||
|
||
Сразу после переноса канон **заполнен не весь**, и это нормально, но обязано
|
||
быть названо, иначе следующий агент примет скелет за поломку.
|
||
|
||
Печатается по факту: сколько документов стоят честной строкой вместо
|
||
содержания, сколько маркеров долга в `architecture.md`, сколько задач без
|
||
критериев приёмки. Закрывается порциями по ходу работы, а не одним заходом.
|
||
|
||
## `upgrade` — канон вырос
|
||
|
||
1. `docs.py version` — версия проекта и версия скрипта.
|
||
2. Проект новее скрипта — **обнови маркетплейс**, а не проект: это отстал
|
||
плагин.
|
||
3. Иначе иди по [changelog.md](references/changelog.md) снизу вверх от версии
|
||
проекта до текущей и делай названное в каждой записи. Записи независимы и
|
||
применяются по порядку.
|
||
4. Подними `canon` в `docs/.pm.json` до текущей.
|
||
5. `docs.py check`.
|
||
|
||
Записи журнала описывают **что сделать проекту**. Если запись этого не говорит —
|
||
это дефект журнала, и о нём надо сказать, а не догадываться.
|
||
|
||
## Чего этот скилл не делает
|
||
|
||
- **Не сочиняет содержание.** Пустой слот получает честную строку о том, что его
|
||
наполнить пока нечем, а не выдуманный абзац. Придуманный периметр модели угроз
|
||
хуже отсутствующего: по нему будут строиться находки.
|
||
- **Не удаляет то, чьё содержимое не нашло дом.** Оригинал живёт, пока не
|
||
названо поимённо, куда переехал каждый его кусок.
|
||
- **Не ведёт содержимое канона** — это скилл `docs`. Здесь только раскладка.
|
||
- **Не заводит проект с нуля** — это скилл `init`.
|
||
- **Не правит историю.** В старых коммитах старые пути остаются, и это нормально.
|
||
|
||
## Доклад
|
||
|
||
- Что нашёл `docs.py`: код выхода и число пунктов дрейфа.
|
||
- Что перенесено: файл → дом, числом и поимённо для спорного.
|
||
- **Удалённые дубли** — с указанием, против какой спеки сверялся каждый.
|
||
- **Не разложилось** — поимённо, с причиной.
|
||
- Переходное состояние числами: честных строк, маркеров долга, задач без
|
||
критериев.
|
||
- **Граница покрытия**: что проверила машина, что судил ты, чего не смотрел
|
||
никто.
|