Files
dev-skills/README.md
T
avandClaude Opus 5 d5bee11a6b классификация задачи: три категории документов и метка вместо ступени
Канон 5 объявил «каждый документ docs/ — тема ревью». Правило верно ровно
наполовину и потому вредно целиком. Паспорт и схему хранилища ревью читает, но
темами они не являются: по ним нельзя сказать «в этом изменении сделано не так»,
они задают границу, по которой судит чужая тема. Журнал решений и журнал
наблюдений ревью изменения не нужны вовсе — ADR объясняет прошлое, а не
предъявляет требование. Разметчик, применявший правило буквально, обязан был
либо завести фантомные темы passport, adr, database, research и продублировать
ими работу architecture и operations, либо потерять четыре документа молча;
случались обе ветки, и в собственном образце плана docs/passport.md не попадал
ни строкой, а обязательная арифметика покрытия при этом не сходилась.

Категорий теперь три, разрез проверяемый. Тема — да, прямо: conventions,
security, architecture и любой свой документ проекта. Источник темы — нет, но он
задаёт границу для чужой: passport, database, CLAUDE.md, openspec/specs.
Процессный — нет, он про то, как мы работаем: tasks, review, adr, research,
.pm.json. Открыта одна категория из трёх, две другие перечислены поимённо, так
что документ вне раскладки — однозначно своя тема. adr и research прогон больше
не открывает ни одним проходом; docs/review остаётся читаемым, но как настройка
конвейера, а не критерий. Цена записана и стала обязательной строкой границ
покрытия: расхождение с записанным решением ловит теперь только сверка
документации, а число под находкой обязано быть снято на этом прогоне, с
приложенной командой.

Классификация выдаёт задаче метку — small, medium, large. Прежние quick,
standard и wide назывались ступенью и описывали ревью: как глубоко смотрим.
Классифицируется же задача, и пока величина называлась свойством прогона, её
естественно было пересчитывать на каждом прогоне — что конвейер и делал. Слово
«ступень» удалено, а не оставлено синонимом: два имени одной вещи расходятся.
Выводится метка из двух разведённых осей — размер (малое, среднее, крупное) и
сложность (знакомое, незнакомое), — и равна максимуму по ним. Метка не синоним
размера: малое незнакомое изменение получает large, трогая один узел, поэтому
план печатает три строки с обоснованием каждая и выводить одну из другой
запрещено. Оси остались русскими словами — это суждение прозой; метка
английская — это идентификатор, который проходы сравнивают.

Разметка переехала из ревью кода в шаг 4 пайплайна, сразу после propose. Она
шла первым проходом каждого ревью кода, а перед ревью дизайна ту же величину
называл сам пайплайн — то есть оркестратор, который только что довёл
предложение до propose. Одно и то же измерялось дважды, и один из двух раз без
разведённости с автором, ровно в той точке, ради которой разметчик заведён.
Теперь запуск один на задачу, диффа он не видит, план обслуживает обе стадии, и
метка после кода не пересматривается: расхождение факта с разметкой ловит журнал
дефектов постфактум, как и всякую другую ошибку выбора. На диск план не пишется —
четвёртый артефакт рядом с proposal, tasks и design пережил бы задачу и разошёлся
бы с ней молча.

Ревью дизайна тоже растёт меткой: small — specs, medium — плюс rubric, large —
плюс architecture и вопрос автору о трёх формах решения. Раньше rubric и
architecture включались одним условием, и medium получал ровно один проход, то
есть не отличался от quick ничем. Разведены они потому, что зарабатывают на
разном: рубрика порождает свойства узла и окупается уже на среднем изменении,
её выход уезжает приёмочными критериями в tasks.md; архитектура отвечает на
вопрос про второй способ, а он на среднем знакомом изменении отвечается «нет»
ещё до запуска.

small подешевел тремя способами сразу. Составом: приёмник тем не запускается,
три темы ядра переходят к code сверкой по записанным инвариантам CLAUDE.md с
потолком в одну находку, и это не «глубина ниже», а другой дом темы. Входом:
specs читает только дельта-спеку, code — только индекс конвенций. Потолком: он
появился у каждого опиниативного прохода, а не у одного basics, и у половин code
он раздельный, потому что конвенционных находок больше по построению и в общем
списке они вытеснили бы техническую половину. Сработавший потолок обязан быть
объявлен строкой — молчащий срез неотличим от «больше не нашлось». Отрицательный
тест small от этого стал жёстче, а не мягче: вопросы про обратимость миграции
задавал приёмник тем, и на этой метке их не задаст никто.

Пайплайн задачи вырос до двенадцати шагов. Тривиальность перестала решать состав
ревью — она влияет только на explore; глубину обеих стадий называет метка.

Проверено прогоном ревьюверов по готовому результату: девять расхождений найдено
и починено — контракт находок печатал старый перечень проходов вместо плана по
темам, три ссылки в task-batch указывали на шаг коммита вместо закрытия, запись
changelog не переводила вопросы, адресованные passport и database, ops и
adversary утверждали, что на нижних метках их вопросы задаёт basics, шаблон
покрытия в review-code зашивал потолки small намертво, триггеры метки рассыпались
на два списка против трёх, тема из директивы CLAUDE.md могла остаться без запуска
исполнителя. Гейт зелёный: фронтматтеры, копии, одиннадцать диаграмм, ruff,
pyrefly; docs.py прогнан на живом фикстуре и печатает категорию в отказе.

Канон повышен до версии 6 с записью, выполнимой upgrade. Решения — 40–44.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 10:57:15 +03:00

379 lines
25 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.
# av-dev-skills
Личный маркетплейс плагинов для разработки. Процесс имени не имеет — он и есть
`av-dev`.
Что решено и почему — [DECISIONS.md](DECISIONS.md). Что осталось сделать —
[TODO.md](TODO.md) и [REMAINING.md](REMAINING.md). Как процесс дошёл до текущей
формы — [HISTORY.md](HISTORY.md).
## Плагины
- **av-dev-pm** — управление продуктом. Владеет всем `docs/`.
- `init` — новый проект: интервью по свободному описанию замысла → первичная
документация;
- `canon` — привести проект к канону документов: `check` / `adopt` /
`upgrade`, плюс скрипт `docs.py`. Там же живёт язык проектных текстов —
информационный стиль, англицизмы, жаргон. Смысловую часть, которой скрипт
не видит, судят два агента: `doc-consistency` (документы между собой и с
openspec) и `doc-code-drift` (документы против кода);
- `docs` — содержимое канона по ходу разработки: ADR из архивного
`design.md`, промоут конвенций, запись в разведку и журнал ревью, чистка
архитектуры;
- `tasks` — задачи и цели каталогом markdown-файлов, у каждой записи тип
(`goal`, `feature`, `fix`, `chore`, `research`), и тип задаёт её схему;
вычитывают их два отдельных прохода: `task-form` (форма записи) и
`doc-wording` (язык);
- `session` — ритуал между спринтами и ведение спринта.
- **av-dev-pipeline** — исполнение. **Требует OpenSpec.**
- `task-pipeline` — задача через полный цикл SDD, от постановки до коммита;
- `task-batch` — несколько задач разом, каждая в своём worktree;
- `review-pipeline` — конвейер ревью **по темам**: документ проекта либо
заводит тему проверки, либо питает чужую тему источником, либо процессный и в
ревью не читается вовсе. Разметка идёт **один раз на задачу**, сразу после
`propose`: агент `review-scope` меряет изменение по двум осям — размер и
сложность — и берёт метку как максимум по ним. Одна метка правит **обе**
стадии ревью: дизайна (`small` — только сверка спек; `medium` — плюс
рубрика; `large` — плюс архитектурный проход) и кода (`small` — гейт, спеки,
код, триаж; `medium` — плюс приёмник тем; `large` — плюс доказательство:
запуск, замер, построенный путь, 5–10% задач). Десять агентов-проходов.
- **av-dev-git** — `commit`: сообщения в личном стиле.
Соглашение об именах: имя **плагина** длинное с префиксом `av-dev-`, имена
**скилов** внутри — короткие. Вызов выходит вида `/av-dev-<плагин>:<скилл>`.
Кто кого зовёт (стрелка — вызов через пространство имён, не импорт):
```mermaid
flowchart TB
subgraph pipe["av-dev-pipeline — исполнение, требует OpenSpec"]
direction LR
batch["task-batch"] --> tp["task-pipeline"]
tp --> rp["review-pipeline<br/>10 агентов-проходов"]
batch --> rp
end
subgraph pm["av-dev-pm — управление продуктом, владеет docs/"]
direction LR
init["init"] --> tasks["tasks"]
canon["canon"] --> tasks
session["session"] --> tasks
docs["docs"]
end
opsx["opsx:* — внешний плагин:<br/>explore, propose, apply, archive"]
git["av-dev-git: commit"]
tp --> opsx
tp --> git
tp --> docs
tp --> tasks
```
Зависимость **односторонняя: `av-dev-pipeline` знает про `av-dev-pm`, обратно —
нет.** Управление продуктом работает в проекте без конвейера; конвейер без
канона деградирует поразрядно и говорит об этом строкой.
## Канон документов проекта
Все проекты приводятся к одной раскладке — так проще ориентироваться, когда
проектов много, и рядом OpenSpec тоже держит строгую структуру. `CLAUDE.md` плюс
`docs/` (паспорт, архитектура, база, безопасность, конвенции, разведка, ADR,
ревью, задачи) и `openspec/` — **раскладка целиком, роли документов и правило
единственного дома живут одним домом**:
[canon.md](av-dev-pm/skills/canon/references/canon.md). Здесь она не
пересказывается: копия перечня путей уже расходилась с домом, и как раз в
обязательных — в ней не хватало путей, чьё отсутствие `docs.py check` считает
нарушением.
**Документы канона делятся на три категории, и разрез проверяемый: можно ли по
документу сказать «в этом изменении сделано не так».** **Тема** — да, прямо
(`conventions`, `security`, `architecture` и любой свой документ проекта; список
тем открытый — завёл документ, завёл направление проверки). **Источник темы**
нет, но он задаёт границу для чужой темы (`passport`, `database`, `CLAUDE.md`,
`openspec/specs/`). **Процессный документ** — нет, он про то, как мы работаем
(`tasks/`, `review.*`, `adr.*`, `research.*`); ревью изменения по нему не судит.
Форма дома — файл или каталог, на выбор проекта.
Отдельного файла-брифа при этом нет — проходы читают документы напрямую; карта
«тема → её дом → что оттуда берётся» —
[project-facts.md](av-dev-pipeline/skills/review-pipeline/references/project-facts.md).
Прийти в старый проект и перевести его на канон — `/av-dev-pm:canon`. Канон
версионируется, и проекты повышаются по [журналу
версий](av-dev-pm/skills/canon/references/changelog.md).
## Подключение
Плагин подключается **на уровне проекта**, чтобы был активен у всех, кто
открывает репозиторий. Из терминала, в каталоге проекта:
```bash
cd /path/to/project
# маркетплейс: один раз на проект. --scope project кладёт его
# в extraKnownMarketplaces этого репозитория (см. ниже), без флага — в user
claude plugin marketplace add https://git.vakhrushev.me/av/dev-skills.git --scope project
# плагины: scope обязателен, умолчание у команды — user, а нам нужен project
claude plugin install av-dev-pm@av-dev-skills --scope project
claude plugin install av-dev-pipeline@av-dev-skills --scope project
claude plugin install av-dev-git@av-dev-skills --scope project
```
Те же команды изнутри Claude Code — со слешем: `/plugin marketplace add …`,
`/plugin install … --scope project`. Обе формы дописывают в
`.claude/settings.json` проекта то, что можно внести и руками:
```json
{
"extraKnownMarketplaces": {
"av-dev-skills": {
"source": { "source": "git", "url": "https://git.vakhrushev.me/av/dev-skills.git" }
}
},
"enabledPlugins": {
"av-dev-pm@av-dev-skills": true,
"av-dev-pipeline@av-dev-skills": true,
"av-dev-git@av-dev-skills": true
}
}
```
**При установке в проект, где лежали проектные копии** скиллов и агентов
(`.claude/skills/` — голые `task-pipeline`, `review-pipeline`, `task-batch`
и с префиксом проекта `<проект>-task-pipeline`,
`.claude/agents/<проект>-review-*.md`) — снеси их. Две копии одного скилла
расходятся, и побеждает та, что короче названа.
## Обновление
**Обновление — два шага, и первого мало.** `marketplace update` тянет git-клон
маркетплейса, но снимки плагинов лежат отдельно, в
`~/.claude/plugins/cache/av-dev-skills/<плагин>/<версия>/`, и обновляются только
командой `plugin update`. Один шаг без второго выглядит как «обновил, а ничего не
изменилось» — так и было в первый раз.
```bash
# 0. отправить свои коммиты: до клона доезжает только то, что на origin
git -C /path/to/dev-skills push origin master
# 1. клон маркетплейса
claude plugin marketplace update av-dev-skills
# 2. снимки плагинов — из каталога проекта, где они установлены
cd /path/to/project
claude plugin update av-dev-pm@av-dev-skills --scope project
claude plugin update av-dev-pipeline@av-dev-skills --scope project
claude plugin update av-dev-git@av-dev-skills --scope project
```
**`cd` в проект обязателен, и это не педантизм.** Команда правит запись реестра, а
записей столько, во скольких проектах плагин установлен; за вызов двигается
**одна**. Запущенная не из проекта, она обновит какую-то из них — наблюдалось:
`av-dev-git` стоял в шести проектах, один вызов поднял версию ровно в одном, и не
в том, из которого звали. Проекты обновляются поштучно.
Что стоит и какой версии — одной командой:
```bash
python3 - <<'EOF'
import json, pathlib
d = json.loads(pathlib.Path.home().joinpath(".claude/plugins/installed_plugins.json").read_text())
for name, entries in sorted(d["plugins"].items()):
if "av-dev" not in name:
continue
for e in entries:
print(f"{e['version']:14} {name:30} {e.get('projectPath', e.get('scope', ''))}")
EOF
```
Версия — первые 12 знаков хеша коммита этого репозитория, так что сверяется
глазами с `git rev-parse HEAD | cut -c1-12`. Команда идемпотентна: на уже свежем
плагине скажет `already at the latest version`.
**Изменения применяются после перезапуска Claude Code** — работающая сессия
держит скиллы в контексте и про новый снимок не знает.
## Снятие
Действие, обратное подключению.
```bash
cd /path/to/project
claude plugin uninstall <плагин>@av-dev-skills --scope project
```
Команда правит два места: убирает строку из `enabledPlugins` в
`.claude/settings.json` проекта и запись из реестра
`~/.claude/plugins/installed_plugins.json`. Снимок в
`~/.claude/plugins/cache/av-dev-skills/<плагин>/<версия>/` не трогает — он
общий для всех проектов. Маркетплейс тоже остаётся: `extraKnownMarketplaces`
нужен остальным плагинам.
**Удалять из маркетплейса можно и до снятия с проектов.** `uninstall` идёт по
реестру, а не по `marketplace.json`, и снимает плагин, записи о котором в
манифесте уже нет. Проверено на `av-dev-backlog`: удалён из маркетплейса,
снят с jellybit после — команда отработала штатно.
`--scope project` обязателен по той же причине, что и при установке: умолчание у
команды — user. `cd` в проект обязателен, но здесь ошибка слышна — вызванная не
оттуда, команда откажется словами `is not installed in project scope`, а не
снимет плагин наугад, как это делает `plugin update`.
**Убрать строку из `settings.json` руками — половина дела:** запись в реестре
переживает такую правку, и в инвентаризации проект продолжает числиться. Лечится
той же командой из каталога проекта — она отработает и когда в `settings.json`
уже пусто.
Снялось или нет — видно инвентаризацией из раздела [Обновление](#обновление).
**Применяется после перезапуска Claude Code**, как и обновление.
## Структура репозитория
```
.claude-plugin/marketplace.json манифест маркетплейса
<plugin>/.claude-plugin/plugin.json манифест плагина
<plugin>/skills/<skill>/SKILL.md скилы (авто-обнаружение)
<plugin>/skills/<skill>/references/ что читается по ссылке из скилла
<plugin>/skills/<skill>/scripts/ tasks.py, docs.py
<plugin>/agents/ charter'ы сабагентов
scripts/ проверки репозитория: копии, диаграммы, фронтматтеры
pyproject.toml линтеры скриптов, только для этого репозитория
lefthook.yml гейт коммита: проверки документов
```
## Проверка скриптов
`tasks.py` и `docs.py` запускаются **где угодно голым `python3` 3.12 без
установки чего-либо** — они лежат рядом со скиллами и работают в любом проекте.
`pyproject.toml` в корне не меняет этого: он живёт только здесь и держит
линтеры, а не зависимости скриптов.
```
uv sync # ставит ruff и pyrefly в .venv, версии прибиты точно
uv run ruff check . # правила; --fix для безопасных починок
uv run pyrefly check # типы
```
Ноль внешних зависимостей охраняется двумя способами: `banned-api` у ruff ловит
частые соблазны по имени, а pyrefly видит окружение, где нет ничего кроме
линтеров, и любой сторонний импорт у него не разрешается. Список запретов —
не перечень мира, настоящий страж второй.
## Проверка фронтматтеров
Фронтматтер читает не человек, а загрузчик: по `name` он разрешает вызов, по
`description` решает, звать ли скилл вообще. **Ошибка здесь не выглядит
ошибкой** — тем же способом, что и в диаграммах.
```
uv run python scripts/frontmatter.py # 0 в порядке, 1 расхождение, 3 не тот каталог
```
Ловится три класса:
- **двоеточие с пробелом в описании без кавычек.** Для YAML `: ` внутри
простого скаляра начинает вложенное отображение, так что «конвейер ревью: гейт,
сверка…» — это не текст с двоеточием, а синтаксическая ошибка. Так было
написано часть описаний плагинов, и читались они правильно — замер и разбор
в [DECISIONS.md](DECISIONS.md), решение III;
- **`name`, разошедшееся с именем каталога скилла или файла charter'а.** Вызов
разрешается по каталогу, а сообщение о промахе говорит «нет такого скилла»,
а не «имя не то»;
- **цвет charter'а, не отвечающий его модели.** Цвет кодирует модель, а не роль
прохода — раскладка живёт в
[review-pipeline/SKILL.md](av-dev-pipeline/skills/review-pipeline/SKILL.md),
разделе «Модель по проходу», здесь только её механизация. Держаться вниманием
правило не может: цвет ставится один раз при заведении charter'а, а модель
потом меняется калибровкой.
## Проверка копий правил
«Один факт — один дом» держалось вниманием и трижды не удержалось. Копии всё же
нужны: скелеты канона уезжают в репозиторий проекта и обязаны там что-то
говорить. Значит копия допустима, но **дословная и помеченная**:
```
uv run python scripts/copies.py # 0 сошлось, 1 расхождение, 2 разметка, 3 не тот каталог
```
Разметка — HTML-комментарии, невидимые в отрендеренном markdown:
```
<!-- дом: <id> --> …текст… <!-- /дом: <id> -->
<!-- копия: <id> из <путь к дому> --> …тот же текст… <!-- /копия: <id> -->
```
Идентификатор — буквы, цифры и дефис, и он повторяется в закрывающем маркере.
Строгость нужна ровно затем, чтобы этот абзац сам не объявил дом: `<id>` под
шаблон не подходит.
Сверяется текст между маркерами; ограда блока кода и пустые строки по краям в
сверку не входят. Маркер, уехавший в проект вместе со скелетом, там полезен: он
говорит, что у текста есть дом и правится он там.
Скрипт ловит четыре вещи: копия разошлась с домом (с диффом), копия указывает не
на тот файл, дом остался без копий, разметка сломана. Чего он **не** ловит —
копию, которую забыли пометить: помечать — по-прежнему решение человека.
## Проверка диаграмм
Диаграммы `mermaid` живут исходником в markdown — картинок в репозитории нет.
Синтаксическая ошибка в блоке **не видна при чтении**: текст выглядит
правдоподобно, диff показывает разумную строку, а рендер падает.
```
uv run python scripts/diagrams.py # весь репозиторий
uv run python scripts/diagrams.py A.md B.md # только названные файлы
# 0 рендерятся, 1 нет, 3 нет mermaid-cli
```
Рендерит `mmdc` с PATH или `npx --yes @mermaid-js/mermaid-cli`; ни того ни
другого нет — код 3, а не молчаливый успех. Это самая дорогая проверка
репозитория: каждый блок — отдельный запуск mermaid-cli со своим chromium,
секунда с лишним. Поэтому у неё два рычага, и оба нужны гейту коммита: **блоки
собираются все сразу, а рендерятся параллельно** (пул потоков, порядок вывода
берётся из порядка сбора), и **проверять можно названные файлы, а не весь
репозиторий**. Весь репозиторий — три секунды вместо пятнадцати, один
файл — одна.
Чего проверка **не** ловит — расхождение диаграммы с прозой вокруг неё. Дословного
соответствия между текстом и графом нет, сличать нечего, и держится это
правилом старшинства, записанным рядом с каждой диаграммой: **в конвейере ревью
старший граф** (он и есть алгоритм планировщика, проза его объясняет), **в
остальных местах старшая проза** (диаграмма там сводка).
## Гейт коммита
Все пять проверок стоят в `pre-commit` через [lefthook](https://lefthook.dev) —
конфиг в [lefthook.yml](lefthook.yml), ставится один раз на клон:
```
lefthook install # пишет .git/hooks/pre-commit
lefthook run pre-commit # прогнать руками, не коммитя
```
| Проверка | Когда идёт | Что смотрит | Сколько |
| --- | --- | --- | --- |
| фронтматтеры | правка `*.md` | весь репозиторий | миллисекунды |
| копии правил | правка `*.md` | весь репозиторий | миллисекунды |
| диаграммы | правка `*.md` | staged-файлы | ~1 с на файл |
| `ruff check --fix` | правка `*.py` | staged-файлы | доли секунды |
| `pyrefly check` | правка `*.py` | staged-файлы | доли секунды |
Glob разводит две половины: коммит, трогающий одни скрипты, не платит за рендер
диаграмм, а коммит в документы не гоняет линтеры.
**Судятся staged-файлы, а не рабочее дерево** — гейт обязан проверять то, что
уедет в историю, а не то, что случайно лежит рядом на диске. Исключений два, и
оба про существо, а не про удобство: `copies.py` сверяет копию с домом, а дом
лежит в другом файле, которого в индексе может не быть (список staged дал бы
«копии дословны» ровно там, где правка дома их и разошлась), а `frontmatter.py`
обходит весь репозиторий за сотые доли секунды — экономить тут нечего.
**`ruff` чинит безопасное сам, и починка доносится до этого же коммита**
(`stage_fixed: true`). Иначе исправленный файл остался бы в рабочем дереве, а в
историю уехал бы невычищенный — худший из исходов: гейт зелёный, коммит грязный.
**Обход разовый — `LEFTHOOK=0 git commit …`.** Он законен ровно для случая,
когда найденное нечем чинить прямо сейчас; молча пропущенная проверка — нет.