avandClaude Opus 5 12882911a9 словарь, манифесты, README: одно слово — одна вещь, одно описание — один дом
- «готовность» значила и «запись можно брать», и «что считается сделанным»;
  второй смысл стал «определением сделанного» — своё же правило про занятое
  слово запрещало это прямо
- «пайплайн» жил в 24 местах вне журналов при том, что DECISIONS фиксирует
  его уход «целиком»; рабочее имя — конвейер
- «чекпоинт» в review значил стадию и проход, в resolve — остановку человеку;
  слово оставлено за остановкой
- у описания плагина было два дома, и три из четырёх уже разошлись. Сведены,
  и класс закрыт машиной: frontmatter.py сверяет plugin.json с marketplace,
  гейт разбужен на *.json
- README врал про односторонние зависимости и терял healthcheck на диаграмме
- перечень агентов в REMAINING отстал на два поколения

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

av-dev-skills

Личный маркетплейс плагинов для разработки. Процесс имени не имеет — он и есть av-dev.

Что решено и почему — DECISIONS.md. Что осталось сделать — TODO.md и REMAINING.md. Как процесс дошёл до текущей формы — HISTORY.md.

Плагины

  • av-dev-docs — документация проекта. Владеет docs/ и CLAUDE.md.
    • init — новый проект: интервью по свободному описанию замысла → первичная документация;
    • canon — привести проект к канону документов: check / adopt / upgrade, плюс скрипт docs.py. Там же лежит копия языка проектных текстов — информационный стиль, англицизмы, жаргон; дом у него общий, shared/language.md;
    • healthcheck — здоровье документации судом, а не машиной: не разошлись ли документы между собой и с кодом. Зовёт двух агентов на весь канон разом — doc-consistency (документы между собой и с openspec) и doc-code-drift (факты против кода) — и разбирает урожай порциями. Дорого, поэтому не на каждой задаче. Язык документов вычитывает отдельный агент doc-wording, и зовут его не отсюда, а те, кто только что писал текст: docs, init и canon;
    • docs — содержимое канона по ходу разработки: ADR из архивного design.md, промоут конвенций, запись в разведку и журнал ревью, чистка архитектуры.
  • av-dev-tasks — учёт работ. Владеет каталогом задач.
    • tasks — задачи и цели каталогом markdown-файлов, у каждой записи тип (goal, feature, fix, chore, research), и тип задаёт её схему; вычитывают их два отдельных прохода: task-form (форма записи) и task-wording (язык записей);
    • groom — груминг беклога: что сейчас самое важное и что перестало быть важным. Ответ записывается порядком строк — приоритет это свойство очереди, и живёт он в индексе. Интерактивный: разбирает вопросы пачкой, переоценивает порциями по 5–8, расставляет верх очереди с доводом на каждое движение.
  • av-dev-code — код по задачам: решение одной задачи и его проверка. Владеет openspec/. Требует OpenSpec и сам его заводит.
    • openspec — завести, настроить и проверить openspec/ в проекте: openspec init, замена примера в config.yaml настройкой канонической формы, скрипт openspec.py (форма файла + сверка слепка с живой версией инструмента). Каталог принадлежит конвейеру, а не канону: без конвейера он проекту не нужен, и docs.py о нём молчит;
    • resolve — одна задача от постановки до закрытия. Обычная идёт циклом SDD с чекпоинтом после ревью дизайна: объяснение человеческим языком, повод скорректировать ход решения. Исследовательская начинается с opsx:explore и чекпоинта вариантов — способы решить, цена каждого, рекомендация; выбор оседает по адресу, который назвала сама задача. Между чекпоинтами — без согласований;
    • review — конвейер ревью по темам: документ проекта либо заводит тему проверки, либо питает чужую тему источником, либо процессный и в ревью не читается вовсе. Разметка идёт один раз на задачу, сразу после propose: агент review-scope меряет изменение по двум осям — размер и сложность — и берёт метку как максимум по ним. Одна метка правит обе стадии ревью: дизайна (small — только сверка спек; medium — плюс рубрика; large — плюс архитектурный проход) и кода (small — гейт, спеки, код, триаж; medium — плюс приёмник тем; large — плюс доказательство: запуск, замер, построенный путь, 5–10% задач). Десять агентов-проходов.
  • av-dev-gitcommit: сообщения в личном стиле.

Соглашение об именах: имя плагина длинное с префиксом av-dev-, имена скилов внутри — короткие. Вызов выходит вида /av-dev-<плагин>:<скилл>.

Кто кого зовёт. Сплошная стрелка — вызов скилла через пространство имён, не импорт; пунктир — совет позвать, а не вызов. Агенты-проходы в графе не показаны: их зовут скиллы, названные выше.

flowchart TB
    subgraph pipe["av-dev-code — исполнение, требует OpenSpec"]
        direction LR
        tp["resolve<br/>2 чекпоинта человеку"] --> rp["review<br/>10 агентов-проходов"]
        osp["openspec<br/>заводит и проверяет openspec/"]
    end
    subgraph docsp["av-dev-docs — документация, владеет docs/"]
        direction LR
        init["init"]
        canon["canon"]
        docs["docs"]
        hc["healthcheck"]
    end
    subgraph tasksp["av-dev-tasks — учёт работ"]
        direction LR
        groom["groom"] --> tasks["tasks"]
    end
    init --> tasks
    init --> osp
    canon --> tasks
    canon --> osp
    canon --> hc
    hc --> tasks
    docs --> rp
    rp --> tasks
    groom -.-> hc
    opsx["opsx:* — внешний плагин:<br/>explore, propose, apply, archive"]
    git["av-dev-git: commit"]

    tp --> opsx
    tp --> git
    tp --> docs
    tp --> tasks

Зависимости взаимные, но каждая мягкая. av-dev-code зовёт обоих соседей; обратные вызовы тоже есть — av-dev-docs:init и av-dev-docs:canon заводят OpenSpec скиллом av-dev-code:openspec, av-dev-docs:docs берёт у av-dev-code:review форму записи журнала дефектов и процедуру промоута, av-dev-docs:canon и av-dev-docs:healthcheck зовут av-dev-tasks:tasks. Мягкая значит, что у любого вызова есть ветка «не разрешился»: соседа в проекте нет — вызывающий называет строкой, чего теперь не делает никто, и работу не останавливает. Как именно зовут соседа и что делают, когда вызов не разрешился, — shared/plugin-boundary.md: правило нужно большинству скиллов, и ни один плагин им не владеет. То, что нужно нескольким дословно — граница плагинов, язык проектных текстов, словарь сопровождения, — живёт домом в shared/ и уезжает в каждый плагин помеченной копией.

Канон документов проекта

Все проекты приводятся к одной раскладке — так проще ориентироваться, когда проектов много, и рядом OpenSpec тоже держит строгую структуру. CLAUDE.md плюс docs/ (паспорт, архитектура, база, безопасность, конвенции, разведка, ADR, ревью, задачи) и openspec/раскладка целиком, роли документов и правило единственного дома живут одним домом: canon.md. Здесь она не пересказывается: копия перечня путей уже расходилась с домом, и как раз в обязательных — в ней не хватало путей, чьё отсутствие docs.py check считает нарушением.

Документы канона делятся на три категории, и разрез проверяемый: можно ли по документу сказать «в этом изменении сделано не так». Тема — да, прямо (conventions, security, architecture и любой свой документ проекта; список тем открытый — завёл документ, завёл направление проверки). Источник темы — нет, но он задаёт границу для чужой темы (passport, database, CLAUDE.md, openspec/specs/). Процессный документ — нет, он про то, как мы работаем (tasks/, review.*, adr.*, research.*); ревью изменения по нему не судит. Форма дома — файл или каталог, на выбор проекта.

Отдельного файла-брифа при этом нет — проходы читают документы напрямую; карта «тема → её дом → что оттуда берётся» — project-facts.md.

Прийти в старый проект и перевести его на канон — /av-dev-docs:canon. Канон версионируется, и проекты повышаются по журналу версий.

Подключение

Плагин подключается на уровне проекта, чтобы был активен у всех, кто открывает репозиторий. Из терминала, в каталоге проекта:

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-docs@av-dev-skills --scope project
claude plugin install av-dev-tasks@av-dev-skills --scope project
claude plugin install av-dev-code@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 проекта то, что можно внести и руками:

{
  "extraKnownMarketplaces": {
    "av-dev-skills": {
      "source": { "source": "git", "url": "https://git.vakhrushev.me/av/dev-skills.git" }
    }
  },
  "enabledPlugins": {
    "av-dev-docs@av-dev-skills": true,
    "av-dev-tasks@av-dev-skills": true,
    "av-dev-code@av-dev-skills": true,
    "av-dev-git@av-dev-skills": true
  }
}

При установке в проект, где лежали проектные копии скиллов и агентов (.claude/skills/ — голые task-pipeline, review-pipeline и с префиксом проекта <проект>-task-pipeline, .claude/agents/<проект>-review-*.md) — снеси их. Две копии одного скилла расходятся, и побеждает та, что короче названа.

Обновление

Обновление — два шага, и первого мало. marketplace update тянет git-клон маркетплейса, но снимки плагинов лежат отдельно, в ~/.claude/plugins/cache/av-dev-skills/<плагин>/<версия>/, и обновляются только командой plugin update. Один шаг без второго выглядит как «обновил, а ничего не изменилось» — так и было в первый раз.

# 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-docs@av-dev-skills --scope project
claude plugin update av-dev-tasks@av-dev-skills --scope project
claude plugin update av-dev-code@av-dev-skills --scope project
claude plugin update av-dev-git@av-dev-skills --scope project

cd в проект обязателен, и это не педантизм. Команда правит запись реестра, а записей столько, во скольких проектах плагин установлен; за вызов двигается одна. Запущенная не из проекта, она обновит какую-то из них — наблюдалось: av-dev-git стоял в шести проектах, один вызов поднял версию ровно в одном, и не в том, из которого звали. Проекты обновляются поштучно.

Что стоит и какой версии — одной командой:

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 — работающая сессия держит скиллы в контексте и про новый снимок не знает.

Снятие

Действие, обратное подключению.

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, openspec.py
<plugin>/agents/                     charter'ы сабагентов
shared/                              дома правил, общих для нескольких плагинов
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 решает, звать ли скилл вообще. Ошибка здесь не выглядит ошибкой — тем же способом, что и в диаграммах.

python3 scripts/frontmatter.py    # 0 в порядке, 1 расхождение, 3 не тот каталог

Ловится четыре класса:

  • двоеточие с пробелом в описании без кавычек. Для YAML : внутри простого скаляра начинает вложенное отображение, так что «конвейер ревью: гейт, сверка…» — это не текст с двоеточием, а синтаксическая ошибка. Так было написано часть описаний плагинов, и читались они правильно — замер и разбор в DECISIONS.md, решение III;
  • name, разошедшееся с именем каталога скилла или файла charter'а. Вызов разрешается по каталогу, а сообщение о промахе говорит «нет такого скилла», а не «имя не то»;
  • цвет charter'а, не отвечающий его модели. Цвет кодирует модель, а не роль прохода — раскладка живёт в review/SKILL.md, разделе «Модель по проходу», здесь только её механизация. Держаться вниманием правило не может: цвет ставится один раз при заведении charter'а, а модель потом меняется калибровкой;
  • описание плагина, разошедшееся между манифестами. У описания два дома: <плагин>/.claude-plugin/plugin.json показывает его установленному плагину, корневой .claude-plugin/marketplace.json — тому, кто выбирает, ставить ли. Правят обычно один, и разойтись они успели уже трижды из четырёх. copies.py этот класс не берёт: он смотрит markdown, а манифест — json. Отсюда и *.json в глобе задачи гейта.

Проверка копий правил

«Один факт — один дом» держалось вниманием и трижды не удержалось. Копии всё же нужны: скелеты канона уезжают в репозиторий проекта и обязаны там что-то говорить. Значит копия допустима, но дословная и помеченная:

python3 scripts/copies.py     # 0 сошлось, 1 расхождение, 2 разметка, 3 не тот каталог

Разметка — HTML-комментарии, невидимые в отрендеренном markdown:

<!-- дом: <id> -->      …текст…   <!-- /дом: <id> -->
<!-- копия: <id> из <путь к дому> -->      …тот же текст…   <!-- /копия: <id> -->

Идентификатор — буквы, цифры и дефис, и он повторяется в закрывающем маркере. Строгость нужна ровно затем, чтобы этот абзац сам не объявил дом: <id> под шаблон не подходит.

Сверяется текст между маркерами; ограда блока кода и пустые строки по краям в сверку не входят. Маркер, уехавший в проект вместе со скелетом, там полезен: он говорит, что у текста есть дом и правится он там.

Дом правила, общего для нескольких плагинов, лежит в shared/ и ни одному из них не принадлежит. Так живёт язык проектных текстов: он одинаково нужен документам канона и задачам, и хранить его внутри одного плагина значило бы отдать общее правило во владение половине. Так же живёт граница плагинов — правило обращения к соседу. Плагин везёт копию и потому остаётся самодостаточным — shared/ нужен этому репозиторию, а не установленному плагину.

Дом ставится в shared/ только тогда, когда владельца нет. У адресов владелец есть: раскладку docs/ держит канон, каталог задач — плагин задач, и переносить их наружу значило бы отобрать у владельца его же предмет. Общее без владельца едет копией из shared/; чужое с владельцем остаётся дома, а потребитель на него ссылается.

Скрипт ловит четыре вещи: копия разошлась с домом (с диффом), копия указывает не на тот файл, дом остался без копий, разметка сломана. Чего он не ловит — копию, которую забыли пометить: помечать — по-прежнему решение человека.

Пересборка — scripts/resync.py

python3 scripts/resync.py     # переписать тела всех разошедшихся копий из домов
# 0 готово, 2 разметка сломана, 3 не тот каталог

Правка дома касается стольких файлов, сколько у него копий, и последний из них забывают — это и есть причина, по которой копии расходятся. Пересборка делает то же машиной и потому дословна по построению.

В гейт коммита скрипт не ставится, и это решение. Автоматическая пересборка протащила бы правку дома во все копии мимо глаз автора, а правка дома, чья копия уезжает в репозиторий проекта, обязана ещё и попасть в журнал версий канона — этого машина не напишет. Гейт поэтому только называет расхождение; согласие с ним остаётся действием человека.

Разметку разбирает не он сам: copies.py импортируется целиком. Второй разборщик той же разметки разошёлся бы с первым молча — ровно тот класс дефекта, против которого механика копий и заведена.

Ограда блока кода принадлежит месту, а не дому. Одно и то же тело живёт в доме внутри ```, а в скелете канона — внутри чужой, объемлющей ограды, и своей там иметь не должно. Пересборка берёт тело дома без крайних оград и надевает обратно ту, что была у копии; пустые строки по краям — так же.

Проверка адресов документов

Адрес документа принадлежит одному плагину, а называют его все: docs/* стоит примерно в сорока местах конвейера, tasks/ROADMAP.md — в четырёх местах канона. Переименование в каноне до этих мест само не доходит.

python3 scripts/addresses.py     # весь репозиторий
# 0 сошлось, 1 упразднённый адрес или опечатка, 3 перечень владельца недоступен

Зачем машина, а не аккуратность. Прогон ревью умеет честно деградировать: дома темы нет — в границах покрытия появляется строка «документа в проекте нет» с названной ценой. Протухший адрес попадает ровно в эту машинерию и выходит правдоподобным отчётом, а не поломкой. Громкий признак ошибки деградацией убран, и здесь он возвращается гейтом.

Перечень берётся из константы владельца — той, по которой он и так проверяет раскладку (docs.py, tasks.py). Второй перечень прозой был бы вторым домом ровно того сорта, против которого написан канон.

Судится упразднённое, а не незнакомое, и это следует из канона: список тем открытый, всё, что проект кладёт в docs/ сверх закрытых категорий, — законная тема, и опровергнуть её нечем. Зато переименование ловится точно: канон, убирая слот, кладёт его в карту переездов, и она здесь и есть перечень запрещённого. Рядом единственная догадка — имя, почти совпавшее с каноническим: securty это опечатка вероятнее, чем новая тема. Порог замерен по репозиторию: законные имена дают до 0.64, опечатки — от 0.91.

Не проверяются журналы (они описывают прошлые состояния и задним числом не переписываются), адреса openspec/* (раскладка чужого инструмента, владельца у нас нет) и упоминания в комментариях скриптов — сверяется только markdown. Эти границы скрипт печатает сам.

Проверка диаграмм

Диаграммы mermaid живут исходником в markdown — картинок в репозитории нет. Синтаксическая ошибка в блоке не видна при чтении: текст выглядит правдоподобно, диff показывает разумную строку, а рендер падает.

python3 scripts/diagrams.py            # весь репозиторий
python3 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 — конфиг в lefthook.yml, ставится один раз на клон:

lefthook install          # пишет .git/hooks/pre-commit
lefthook run pre-commit   # прогнать руками, не коммитя
Проверка Когда идёт Что смотрит Сколько
фронтматтеры правка *.md или *.json весь репозиторий миллисекунды
копии правил правка *.md весь репозиторий миллисекунды
адреса документов каждый коммит весь репозиторий ~0.07 с
диаграммы правка *.md staged-файлы ~1 с на файл
ruff check --fix правка *.py staged-файлы доли секунды
pyrefly check правка *.py staged-файлы доли секунды

Glob разводит две половины: коммит, трогающий одни скрипты, не платит за рендер диаграмм, а коммит в документы не гоняет линтеры.

Судятся staged-файлы, а не рабочее дерево — гейт обязан проверять то, что уедет в историю, а не то, что случайно лежит рядом на диске. Исключений три, и все про существо, а не про удобство: copies.py сверяет копию с домом, а дом лежит в другом файле, которого в индексе может не быть (список staged дал бы «копии дословны» ровно там, где правка дома их и разошлась); frontmatter.py обходит весь репозиторий за сотые доли секунды — экономить тут нечего; addresses.py идёт без glob вовсе, потому что сводит две стороны: перечень адресов лежит в *.py владельца, а упоминания — в *.md соседей, и коммит с переименованием документа трогает только первую.

ruff чинит безопасное сам, и починка доносится до этого же коммита (stage_fixed: true). Иначе исправленный файл остался бы в рабочем дереве, а в историю уехал бы невычищенный — худший из исходов: гейт зелёный, коммит грязный.

resync.py в гейте нет намеренно — он чинит, а не проверяет, и его правка обязана быть прочитана глазами (см. выше).

Обход разовый — LEFTHOOK=0 git commit …. Он законен ровно для случая, когда найденное нечем чинить прямо сейчас; молча пропущенная проверка — нет.

S
Description
No description provided
Readme
3.5 MiB
Languages
Python 100%