diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index bac4eec..4bc9fed 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -8,17 +8,17 @@ { "name": "av-dev-docs", "source": "./av-dev-docs", - "description": "Документация проекта: канон раскладки docs/ и CLAUDE.md, роли документов, правило единственного дома. check / adopt / upgrade со скриптом docs.py, старт проекта интервью по брифу, ведение содержимого по ходу разработки, healthcheck — сверка документов между собой и с кодом судом двух агентов. Ничего не выполняет сам и никакого пайплайна не требует." + "description": "Документация проекта: канон раскладки (CLAUDE.md плюс docs/ — паспорт, архитектура, схема БД, безопасность, конвенции, разведка, ADR, журнал ревью), роли документов и правило единственного дома. Три операции одной машиной сравнения — check, adopt, upgrade — со скриптом docs.py; заведение нового проекта интервью по брифу; ведение содержимого по ходу разработки: синк после сделанной задачи, ADR промоутом из архивного design.md, записка разведки, запись дефекта в журнал ревью. Смысловое, чего скрипт не видит, судит скилл healthcheck двумя агентами разом — doc-consistency (документы между собой и с openspec) и doc-code-drift (факты против кода); язык документов вычитывает агент doc-wording. Задач не ведёт — это плагин av-dev-tasks, и он опционален." }, { "name": "av-dev-tasks", "source": "./av-dev-tasks", - "description": "Задачи и цели каталогом markdown-файлов, у каждой записи тип, и тип задаёт её схему. Приоритет — порядок строк в беклоге, расставляет его скилл груминга. Проверка согласованности скриптом tasks.py. Задача выполняется чем угодно: пайплайна плагин не требует и сам его не зовёт." + "description": "Задачи и цели каталогом markdown-файлов: одна запись — файл в items/ плюс строка ровно в одном индексе, у записи тип (goal, feature, fix, chore, research), и тип решает, каких разделов она требует и что с ней можно делать. Заведение из диалога с фильтром и дедупом, разбор находок ревью в задачи, декомпозиция на независимо полезные части, гигиена полей, согласованность индексов скриптом tasks.py. Приоритет — явный порядок строк в беклоге, и расставляет его скилл groom: интерактивный разбор на два вопроса, что сейчас самое важное и что перестало быть важным. Готовность записи к работе проверяет команда ready. Записи вычитывают два прохода: task-form (форма записи) и task-wording (язык). Канон документов ведёт плагин av-dev-docs, он опционален. Задач не выполняет — этим занимается конвейер проекта." }, { "name": "av-dev-code", "source": "./av-dev-code", - "description": "Решение одной задачи от постановки до закрытия: цикл SDD с чекпоинтом объяснения после ревью дизайна, у исследовательской задачи — ещё и чекпоинт вариантов до первого требования. Конвейер ревью с обязательным триажем. Требует OpenSpec. Задача принимается и обычным текстом; плагины av-dev-docs и av-dev-tasks опциональны — первый даёт документы канона для проходов ревью, второй учёт задач, без них прогон деградирует поразрядно и говорит об этом." + "description": "Решение одной задачи от постановки до закрытия скиллом resolve — полный цикл Spec Driven Development с двумя плановыми остановками: чекпоинт вариантов у исследовательской задачи и чекпоинт с объяснением человеческим языком после ревью дизайна у всякой. Между ними работа идёт без согласований. Плюс конвейер ревью с детерминированным гейтом, сверкой со спеками, враждебными постановками, эксплуатационным постмортемом и обязательным триажем. Требует OpenSpec и сам его заводит скиллом openspec. Задача принимается и обычным текстом. Плагины av-dev-docs и av-dev-tasks опциональны: первый даёт документы канона, из которых проходы читают проектную конкретику, второй — учёт задач; без них прогон деградирует поразрядно и называет это строкой." }, { "name": "av-dev-git", diff --git a/README.md b/README.md index b7b4eb6..0f4de04 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,9 @@ ли документы между собой и с кодом. Зовёт двух агентов на весь канон разом — `doc-consistency` (документы между собой и с openspec) и `doc-code-drift` (факты против кода) — и разбирает урожай порциями. Дорого, поэтому не на - каждой задаче; язык документов вычитывает отдельный агент `doc-wording`; + каждой задаче. Язык документов вычитывает отдельный агент `doc-wording`, и + зовут его не отсюда, а те, кто только что писал текст: `docs`, `init` и + `canon`; - `docs` — содержимое канона по ходу разработки: ADR из архивного `design.md`, промоут конвенций, запись в разведку и журнал ревью, чистка архитектуры. @@ -61,7 +63,9 @@ Соглашение об именах: имя **плагина** длинное с префиксом `av-dev-`, имена **скилов** внутри — короткие. Вызов выходит вида `/av-dev-<плагин>:<скилл>`. -Кто кого зовёт (стрелка — вызов через пространство имён, не импорт): +Кто кого зовёт. Сплошная стрелка — вызов скилла через пространство имён, не +импорт; пунктир — совет позвать, а не вызов. Агенты-проходы в графе не показаны: +их зовут скиллы, названные выше. ```mermaid flowchart TB @@ -75,6 +79,7 @@ flowchart TB init["init"] canon["canon"] docs["docs"] + hc["healthcheck"] end subgraph tasksp["av-dev-tasks — учёт работ"] direction LR @@ -84,6 +89,11 @@ flowchart TB init --> osp canon --> tasks canon --> osp + canon --> hc + hc --> tasks + docs --> rp + rp --> tasks + groom -.-> hc opsx["opsx:* — внешний плагин:
explore, propose, apply, archive"] git["av-dev-git: commit"] @@ -93,9 +103,14 @@ flowchart TB tp --> tasks ``` -Зависимости **односторонние: `av-dev-code` знает про `av-dev-docs` и -`av-dev-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`: правило нужно большинству скиллов, и ни один плагин им не владеет. То, что нужно нескольким дословно — граница плагинов, язык проектных текстов, словарь сопровождения, — живёт домом в @@ -292,17 +307,17 @@ uv run pyrefly check # типы линтеров, и любой сторонний импорт у него не разрешается. Список запретов — не перечень мира, настоящий страж второй. -## Проверка фронтматтеров +## Проверка фронтматтеров и описаний плагинов Фронтматтер читает не человек, а загрузчик: по `name` он разрешает вызов, по `description` решает, звать ли скилл вообще. **Ошибка здесь не выглядит ошибкой** — тем же способом, что и в диаграммах. ``` -uv run python scripts/frontmatter.py # 0 в порядке, 1 расхождение, 3 не тот каталог +python3 scripts/frontmatter.py # 0 в порядке, 1 расхождение, 3 не тот каталог ``` -Ловится три класса: +Ловится четыре класса: - **двоеточие с пробелом в описании без кавычек.** Для YAML `: ` внутри простого скаляра начинает вложенное отображение, так что «конвейер ревью: гейт, @@ -317,7 +332,13 @@ uv run python scripts/frontmatter.py # 0 в порядке, 1 расхожд [review/SKILL.md](av-dev-code/skills/review/SKILL.md), разделе «Модель по проходу», здесь только её механизация. Держаться вниманием правило не может: цвет ставится один раз при заведении charter'а, а модель - потом меняется калибровкой. + потом меняется калибровкой; +- **описание плагина, разошедшееся между манифестами.** У описания два дома: + `<плагин>/.claude-plugin/plugin.json` показывает его установленному плагину, + корневой `.claude-plugin/marketplace.json` — тому, кто выбирает, ставить ли. + Правят обычно один, и разойтись они успели уже трижды из четырёх. `copies.py` + этот класс не берёт: он смотрит markdown, а манифест — json. Отсюда и `*.json` + в глобе задачи гейта. ## Проверка копий правил @@ -326,7 +347,7 @@ uv run python scripts/frontmatter.py # 0 в порядке, 1 расхожд говорить. Значит копия допустима, но **дословная и помеченная**: ``` -uv run python scripts/copies.py # 0 сошлось, 1 расхождение, 2 разметка, 3 не тот каталог +python3 scripts/copies.py # 0 сошлось, 1 расхождение, 2 разметка, 3 не тот каталог ``` Разметка — HTML-комментарии, невидимые в отрендеренном markdown: @@ -429,8 +450,8 @@ python3 scripts/addresses.py # весь репозиторий правдоподобно, диff показывает разумную строку, а рендер падает. ``` -uv run python scripts/diagrams.py # весь репозиторий -uv run python scripts/diagrams.py A.md B.md # только названные файлы +python3 scripts/diagrams.py # весь репозиторий +python3 scripts/diagrams.py A.md B.md # только названные файлы # 0 рендерятся, 1 нет, 3 нет mermaid-cli ``` @@ -461,7 +482,7 @@ lefthook run pre-commit # прогнать руками, не коммитя | Проверка | Когда идёт | Что смотрит | Сколько | | --- | --- | --- | --- | -| фронтматтеры | правка `*.md` | весь репозиторий | миллисекунды | +| фронтматтеры | правка `*.md` или `*.json` | весь репозиторий | миллисекунды | | копии правил | правка `*.md` | весь репозиторий | миллисекунды | | адреса документов | **каждый коммит** | весь репозиторий | ~0.07 с | | диаграммы | правка `*.md` | staged-файлы | ~1 с на файл | @@ -472,8 +493,8 @@ Glob разводит две половины: коммит, трогающий диаграмм, а коммит в документы не гоняет линтеры. **Судятся staged-файлы, а не рабочее дерево** — гейт обязан проверять то, что -уедет в историю, а не то, что случайно лежит рядом на диске. Исключений два, и -три, и все про существо, а не про удобство: `copies.py` сверяет копию с домом, а +уедет в историю, а не то, что случайно лежит рядом на диске. Исключений три, и +все про существо, а не про удобство: `copies.py` сверяет копию с домом, а дом лежит в другом файле, которого в индексе может не быть (список staged дал бы «копии дословны» ровно там, где правка дома их и разошлась); `frontmatter.py` обходит весь репозиторий за сотые доли секунды — экономить тут нечего; diff --git a/REMAINING.md b/REMAINING.md index 320f928..45e6181 100644 --- a/REMAINING.md +++ b/REMAINING.md @@ -81,10 +81,11 @@ check` сверяет версию, но не то, что миграционн только её последствия. **Не выродились ли «границы покрытия» в шаблон.** Строка «что смотрели и чего не -смотрели» обязательна в докладе `check`, `adopt`, интейка, штурма, сессии, -спринта и всех четырёх агентов — семь и больше раз за сессию, и проверить её -исполнение некому: приёмщик и исполнитель одно лицо (`groom/SKILL.md`, -«Стимулы»). Выродившаяся строка **хуже отсутствия**: доклад выглядит проверенным. +смотрели» обязательна в докладе `check`, `adopt`, интейка, штурма и всех пяти +агентов канона и задач (`doc-consistency`, `doc-code-drift`, `doc-wording`, +`task-form`, `task-wording`) — девять мест, и проверить её исполнение некому: +приёмщик и исполнитель одно лицо (`groom/SKILL.md`, «Стимулы»). Выродившаяся +строка **хуже отсутствия**: доклад выглядит проверенным. Приём не правится: это гипотеза об износе, а не находка, и менять работающее по догадке дороже. **Наблюдение к первой обкатке на живом проекте:** если в трёх @@ -93,10 +94,11 @@ check` сверяет версию, но не то, что миграционн **Форма ADR при пересмотре решения.** Парный статус («старая запись получает `заменено на`») судит агент `doc-consistency` — правило 6 его устава. Охват был -открытым вопросом, пока агент зовётся пачкой, отобранной работой; переезд вызова -на сессию с пачкой «весь канон» его снял. Остаётся зазор в спринт и отсутствие -механической проверки — то есть пересмотр, сделанный сегодня, судится на -ближайшей сессии, а не в момент правки. +открытым вопросом, пока агент звался пачкой, отобранной работой; переезд вызова +в `av-dev-docs:healthcheck` с пачкой «весь канон» его снял. Остаётся зазор до +ближайшего прогона `healthcheck` и отсутствие механической проверки — то есть +пересмотр, сделанный сегодня, судится тогда, когда позовут сверку, а не в момент +правки. ## Известные пределы — приняты, чинить не планируется diff --git a/av-dev-code/.claude-plugin/plugin.json b/av-dev-code/.claude-plugin/plugin.json index 6d83d3c..f0bf1a1 100644 --- a/av-dev-code/.claude-plugin/plugin.json +++ b/av-dev-code/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "av-dev-code", - "description": "Решение одной задачи от постановки до закрытия скиллом resolve — полный цикл Spec Driven Development с двумя плановыми остановками: чекпоинт вариантов у исследовательской задачи и чекпоинт с объяснением человеческим языком после ревью дизайна у всякой. Между ними работа идёт без согласований. Плюс конвейер ревью с детерминированным гейтом, сверкой со спеками, враждебными постановками, эксплуатационным постмортемом, независимой реализацией и обязательным триажем. Требует OpenSpec и сам его заводит скиллом openspec. Задача принимается и обычным текстом. Плагины av-dev-docs и av-dev-tasks опциональны: первый даёт документы канона, из которых проходы читают проектную конкретику, второй — учёт задач; без них прогон деградирует поразрядно и называет это строкой.", + "description": "Решение одной задачи от постановки до закрытия скиллом resolve — полный цикл Spec Driven Development с двумя плановыми остановками: чекпоинт вариантов у исследовательской задачи и чекпоинт с объяснением человеческим языком после ревью дизайна у всякой. Между ними работа идёт без согласований. Плюс конвейер ревью с детерминированным гейтом, сверкой со спеками, враждебными постановками, эксплуатационным постмортемом и обязательным триажем. Требует OpenSpec и сам его заводит скиллом openspec. Задача принимается и обычным текстом. Плагины av-dev-docs и av-dev-tasks опциональны: первый даёт документы канона, из которых проходы читают проектную конкретику, второй — учёт задач; без них прогон деградирует поразрядно и называет это строкой.", "author": { "name": "Anton Vakhrushev", "email": "anwinged@gmail.com" diff --git a/av-dev-code/skills/openspec/SKILL.md b/av-dev-code/skills/openspec/SKILL.md index c781307..cfd1a39 100644 --- a/av-dev-code/skills/openspec/SKILL.md +++ b/av-dev-code/skills/openspec/SKILL.md @@ -157,7 +157,7 @@ OpenSpec заводит человек командой выше. ## Чего этот скилл не делает -- **Не пишет спеки и предложения.** Это `opsx:propose` и пайплайн задачи. +- **Не пишет спеки и предложения.** Это `opsx:propose` и конвейер задачи. - **Не ведёт документы канона** — их дом плагин `av-dev-docs`, и адреса в `context` только на них ссылаются. - **Не чинит расхождение формы с версией OpenSpec в проекте.** Оно чинится в diff --git a/av-dev-code/skills/resolve/SKILL.md b/av-dev-code/skills/resolve/SKILL.md index 38c743f..1c817e9 100644 --- a/av-dev-code/skills/resolve/SKILL.md +++ b/av-dev-code/skills/resolve/SKILL.md @@ -20,10 +20,10 @@ description: "Решить одну задачу от постановки до шаги 2, 6 и 8, шаг Р2 разведки, проход `review-specs` и ревью дизайна (они завязаны на `openspec/changes//specs/*/spec.md` и на `openspec validate --strict`). **Проект без OpenSpec этим скиллом не ведётся** — - подключай OpenSpec, а не вырождай цикл: ветка деградации здесь не пишется, - потому что непроверенная ветка деградации хуже честного отказа. Заводить - руками не надо: каталог и настройку в `config.yaml` делает скилл - `av-dev-code:openspec`. + подключай OpenSpec, а не вырождай цикл; почему ветка деградации здесь не + пишется, сказано в `av-dev-code:review`, раздел «Предпосылки», и дом у этого + довода там. Заводить руками не надо: каталог и настройку в `config.yaml` + делает скилл `av-dev-code:openspec`. - **Проектные копии этих скиллов и агентов удаляются при установке плагина** (`.claude/skills/` — голые имена `resolve`, `review`, а у проектов прошлого поколения ещё `task-pipeline`, `review-pipeline`, `task-batch`, и с префиксом @@ -212,9 +212,9 @@ flowchart TD - **Форматом задач.** Индексы руками не правятся, путь к скрипту учёта не выдумывается: этим владеет `av-dev-tasks:tasks` (шаг 11). Закрытие — работа этого скилла, и это осознанное решение с названной ценой: **приёмщик и - исполнитель совпали**. Закрытие поэтому **не окончательно** — человек на сессии - возвращает задачу `reopen` с причиной, а доклад по критериям приёмки становится - единственным, по чему приёмка вообще возможна. + исполнитель совпали**. Закрытие поэтому **не окончательно** — человек на + груминге (`av-dev-tasks:groom`) возвращает задачу `reopen` с причиной, а доклад + по критериям приёмки становится единственным, по чему приёмка вообще возможна. - **Заведением задач из урожая ревью.** Отложенные находки отдаются **списком**; превращать их в задачи — работа `av-dev-tasks:tasks`, у него на этот вход отдельный сценарий «задачи из ревью и аудита». Плагина нет — урожай остаётся @@ -226,7 +226,7 @@ flowchart TD Ровно четыре, и каждый обязан быть назван в докладе прямо: -- **сделана** — определение готовности выполнено целиком; +- **сделана** — определение сделанного выполнено целиком; - **не доведена** — с причиной и с записанным вопросом; что именно сделано и до какой границы, названо явно. Сюда же попадает чекпоинт, на котором человек решение не одобрил; @@ -236,7 +236,7 @@ flowchart TD по названному адресу, кода задача не потребовала. Это полноправный исход, а не недоведённая работа. -## Определение готовности +## Определение сделанного Задача сделана, когда верно всё: @@ -394,7 +394,7 @@ flowchart TD | `large` | `specs`, `rubric`, `architecture` + вопрос автору о трёх формах решения | `review-specs` в режиме «дизайн ДО кода» идёт **на каждой задаче**: это самый -дешёвый чекпоинт конвейера, и он ловит то, что на готовом коде уже не чинят. +дешёвый проход конвейера, и он ловит то, что на готовом коде уже не чинят. Остальные включаются меткой, потому что стадия стоит на каждой задаче и каждый лишний проход здесь умножается на число задач. diff --git a/av-dev-code/skills/review/SKILL.md b/av-dev-code/skills/review/SKILL.md index 8deba07..ac52add 100644 --- a/av-dev-code/skills/review/SKILL.md +++ b/av-dev-code/skills/review/SKILL.md @@ -1,6 +1,6 @@ --- name: review -description: "Конвейер ревью изменения, устроенный по темам: документ проекта либо заводит тему ревью, либо питает чужую тему источником, либо процессный и в ревью не читается вовсе. Ядро тем — requirements, autotests, conventions, architecture, security, operations; список тем открытый, свои темы проект заводит документом. Разметка задачи идёт один раз, после propose: агент review-scope выводит размер и сложность, из их максимума — метка, и раздаёт темы проходам обеих стадий. Метка правит и ревью дизайна (small — только specs; medium — плюс rubric; large — плюс architecture), и ревью кода (small — гейт, спеки, код, триаж; medium — плюс приёмник тем; large — плюс доказательство: враждебные постановки, эксплуатационный постмортем, архитектурный проход на широком входе). Триаж обязателен всегда. Порядок прогона — граф зависимостей: гейт открывает опиниативные проходы, проходы с пометкой «держит машину» идут цепочкой, триаж — единственный сток. Проектная специфика приходит из документов канона av-dev-docs. Вызывается из скилла resolve — двумя чекпоинтами: ревью дизайна до кода и ревью кода после apply." +description: "Конвейер ревью изменения, устроенный по темам: документ проекта либо заводит тему ревью, либо питает чужую тему источником, либо процессный и в ревью не читается вовсе. Ядро тем — requirements, autotests, conventions, architecture, security, operations; список тем открытый, свои темы проект заводит документом. Разметка задачи идёт один раз, после propose: агент review-scope выводит размер и сложность, из их максимума — метка, и раздаёт темы проходам обеих стадий. Метка правит и ревью дизайна (small — только specs; medium — плюс rubric; large — плюс architecture), и ревью кода (small — гейт, спеки, код, триаж; medium — плюс приёмник тем; large — плюс доказательство: враждебные постановки, эксплуатационный постмортем, архитектурный проход на широком входе). Триаж обязателен всегда. Порядок прогона — граф зависимостей: гейт открывает опиниативные проходы, проходы с пометкой «держит машину» идут цепочкой, триаж — единственный сток. Проектная специфика приходит из документов канона av-dev-docs. Вызывается из скилла resolve — двумя стадиями: ревью дизайна до кода и ревью кода после apply." --- # Конвейер ревью @@ -44,7 +44,7 @@ description: "Конвейер ревью изменения, устроенны - **OpenSpec — жёсткая предпосылка, а не опция.** Ревью дизайна, проход `review-specs` и - вызывающий пайплайн задачи завязаны на дельта-спеки + вызывающий скилл `av-dev-code:resolve` завязаны на дельта-спеки (`openspec/changes//specs/*/spec.md`), на актуальные спеки (`openspec/specs/`) и на `openspec validate --strict`. В проекте без OpenSpec шаги, зовущие `opsx:explore` / `opsx:propose` / `opsx:apply` / `opsx:archive`, @@ -624,7 +624,7 @@ flowchart TD **План живёт в контексте прогона задачи и на диск не пишется.** Файл-план был бы четвёртым артефактом рядом с `proposal.md`, `tasks.md` и `design.md`, жил бы -дольше задачи и расходился бы с ней молча. Прервали пайплайн — разметка +дольше задачи и расходился бы с ней молча. Прервали прогон задачи — разметка повторяется; это самый дешёвый проход конвейера, и платить за его вечность дороже, чем перезапустить. @@ -848,7 +848,7 @@ Recall темы `conventions` равен длине конвенций прое ## Ревью дизайна — до кода -Запускается на первом чекпоинте ревью (шаг 4 скилла +Запускается на первой стадии ревью (шаг 4 скилла `av-dev-code:resolve`), когда change уже имеет `proposal.md` и дельта-спеки, но кода ещё нет. Разметка задачи к этому моменту уже прошла — она шагом раньше, и метка известна. @@ -864,7 +864,7 @@ Recall темы `conventions` равен длине конвенций прое | `large` — крупное или незнакомое | `specs`, `rubric`, `architecture` + вопрос автору | **3** | - **всегда** — `review-specs` в режиме «дизайн ДО кода». Дельта-спеки сверяются - на каждой задаче: это самый дешёвый чекпоинт конвейера, и он ловит то, что на + на каждой задаче: это самый дешёвый проход конвейера, и он ловит то, что на готовом коде уже не чинят; - **со `medium`** — `review-rubric`: рубрика на задуманный узел, по ней же разбирается дельта-спека, а сами пункты уезжают приёмочными критериями в @@ -886,14 +886,15 @@ Recall темы `conventions` равен длине конвенций прое отвечается «нет» ещё до запуска. Держать её ниже `large` значит платить за предсказуемый ответ на каждой задаче. -Причина меток — арифметика, а не экономия на осторожности. Чекпоинт стоит +Причина меток — арифметика, а не экономия на осторожности. Стадия стоит **на каждой задаче**, поэтому каждый проход здесь умножается на число задач, и при мелкой нарезке это самая большая статья конвейера. **Граф этой стадии свой, и он плоский.** Гейта нет — кода ещё нет, запускать нечего; метка уже названа разметкой задачи; машину не держит ни один проход; -сток — не триаж, а шаг пайплайна задачи, где замечания отрабатываются правкой -спек. Триаж здесь не нужен: находок единицы, и каждая либо правит спеку, либо +сток — не триаж, а шаг скилла `av-dev-code:resolve`, где замечания +отрабатываются правкой спек. Триаж здесь не нужен: находок единицы, и каждая +либо правит спеку, либо становится развилкой. ```mermaid @@ -904,7 +905,7 @@ flowchart TD rubric["rubric → приёмочные критерии в tasks.md"] arch["architecture на предложении"] author["вопрос автору: три формы решения и компромисс каждой"] - fix["шаг пайплайна: правка спек, развилки — вопросом в запись"] + fix["шаг resolve: правка спек, развилки — вопросом в запись"] plan --> proposal proposal --> specs @@ -938,7 +939,7 @@ flowchart TD - Оркестратор чинит помеченное `Действие: инлайн` и **не логирует мелочь**. - `Действие: развилка` — вопросом с вариантами и ценой каждого туда, где проект - держит вопросы (это знает вызвавший пайплайн, а не конвейер). Оркестратор не + держит вопросы (это знает вызвавший скилл, а не конвейер ревью). Оркестратор не останавливается: он урезает изменение до остатка и доводит его. - Находка не для этого мерджа, но реальная (отложенный `major`, развилка, решённая «потом»), — не теряется, но **и не заводится здесь**. Конвейер отдаёт @@ -960,10 +961,10 @@ flowchart TD заведено: нулевой урожай при непустом отчёте виден сразу. **Вместе с изменением он и переезжает:** после `opsx:archive` его адрес — `openspec/changes/archive//review/`. Кто ищет отчёт после архивации - (приёмщик на сессии, разбор дефекта), смотрит **оба** пути; «отчёта нет» - объявляется, только когда пуст и архивный, иначе самый дорогой сценарий - «состав ревью неизвестен, гоняем заново» срабатывает на каждой доведённой - задаче. + (приёмщик на груминге `av-dev-tasks:groom`, разбор дефекта), смотрит **оба** + пути; «отчёта нет» объявляется, только когда пуст и архивный, иначе самый + дорогой сценарий «состав ревью неизвестен, гоняем заново» срабатывает на + каждой доведённой задаче. ## Честный предел @@ -1030,7 +1031,7 @@ flowchart TD сверкой и доказательством лежит весь класс дефектов, который виден только построенным путём, — и он проверяется на 5–10% задач. -Это сознательная сделка, а не пробел в устройстве: цес меткой `large` платится на +Это сознательная сделка, а не пробел в устройстве: цена метки `large` платится на каждой задаче, а окупается на немногих. Проверяется сделка не рассуждением, а журналом дефектов: если класс, который ловят только меряющие проходы, начал всплывать после мерджа — метку выбирают слишком низко. diff --git a/av-dev-code/skills/review/references/project-facts.md b/av-dev-code/skills/review/references/project-facts.md index d8bd6d6..0bb6920 100644 --- a/av-dev-code/skills/review/references/project-facts.md +++ b/av-dev-code/skills/review/references/project-facts.md @@ -132,5 +132,6 @@ на диск и на СУБД» экономит обязательный вопрос. Отсутствие строки — не факт, а пробел, и его надо назвать в границах покрытия. - **Свойство, ставшее правилом линтера, из конвенций удалено** и лежит в - перечне механизированного в `docs/conventions/README.md`. Проверять его - проходом — тратить внимание на уже проверенное. + перечне механизированного — в `docs/conventions/README.md`, если конвенции + каталогом, и отдельным разделом `docs/conventions.md`, если файлом. Проверять + его проходом — тратить внимание на уже проверенное. diff --git a/av-dev-code/skills/review/references/promote.md b/av-dev-code/skills/review/references/promote.md index 35dd44c..205d02a 100644 --- a/av-dev-code/skills/review/references/promote.md +++ b/av-dev-code/skills/review/references/promote.md @@ -18,7 +18,7 @@ flowchart TD no["промоуту не подлежит:
место одно — комментарий в коде;
вкусовщина — вон на триаже;
нужен рантайм — в журнал ревью"] conv["конвенция:
проверяемое свойство + какой проход нашёл"] rule["правило линтера, запретитель,
тест-сканер или анализатор"] - clean["шаг 3: формулировка удалена из конвенций,
строка — в conventions/README.md"] + clean["шаг 3: формулировка удалена из конвенций,
строка — в перечень механизированного"] f --> cond cond -->|нет| no @@ -85,8 +85,9 @@ flowchart TD - из файла конвенций убирается формулировка правила; остаётся, если нужно, одна строка «проверяется линтером `<имя>`» — но только там, где без неё раздел теряет связность; -- правило переезжает в **перечень механизированного в - `docs/conventions/README.md`** — со ссылкой на место механизации: конфиг +- правило переезжает в **перечень механизированного в доме конвенций** + (`docs/conventions/README.md` у каталога, отдельный раздел + `docs/conventions.md` у файла) — со ссылкой на место механизации: конфиг линтера, собственный анализатор, тест-сканер исходников. Не названное место означает, что проход будет добросовестно проверять уже проверенное; - из контекста инструмента спек убирается дубль, если он там был. diff --git a/av-dev-docs/agents/doc-consistency.md b/av-dev-docs/agents/doc-consistency.md index 85ad5c5..f6c5976 100644 --- a/av-dev-docs/agents/doc-consistency.md +++ b/av-dev-docs/agents/doc-consistency.md @@ -180,6 +180,14 @@ color: yellow ## Доклад +**Форма параллельна дому `вычитка-доклад` (`shared/language.md`), но копией не +является, и маркера здесь нет намеренно.** Копию того дома везут проходы вычитки +— `doc-wording` и `task-wording`; у судьи утверждений расходится каждое поле: +находка стоит на **паре** документов, а не на одном, несёт **дом по канону** и не +несёт «почему», а границы покрытия считают документы и спрашивают про спеки и +архив изменений, а не про термины. Одинаков только порядок разделов, и сверять +машиной в нём нечего. + Находки по одной, в порядке важности: прямые противоречия → факт в двух домах → поведение в обзоре → ADR и провенанс → пустые слоты. Первые ломают решения, которые по документам принимают; последние — только цену чтения. diff --git a/av-dev-docs/skills/canon/SKILL.md b/av-dev-docs/skills/canon/SKILL.md index ee250e5..9abe9c9 100644 --- a/av-dev-docs/skills/canon/SKILL.md +++ b/av-dev-docs/skills/canon/SKILL.md @@ -219,7 +219,8 @@ capability), `openspec/config.yaml`. каталога показывает только `tasks.py check`. Позвал на шаге 5 скилл задач — его отчёт идёт в доклад отдельной строкой, и пункт «задачи без цели» в нём зелёным не станет: цели не сочиняются адаптацией (запрет записан у того, кто - ведёт задачи), их проставляет человек порциями переоценки на первой сессии. + ведёт задачи), их проставляет человек порциями переоценки на первом груминге — + скилл `av-dev-tasks:groom`. ### 5. Объяви переходное состояние diff --git a/av-dev-docs/skills/canon/references/skeletons.md b/av-dev-docs/skills/canon/references/skeletons.md index 61a3be4..e384f28 100644 --- a/av-dev-docs/skills/canon/references/skeletons.md +++ b/av-dev-docs/skills/canon/references/skeletons.md @@ -410,7 +410,7 @@ severity стоит здесь, а не выводится каждым прох - **Что считается сломанным** — какая красная проверка обгоняет развитие, то есть останавливает текущую работу: - **Ориентир по размеру порции:** своё число, если замерялось -- **Что такое «сделана»:** пайплайн проекта пройден + критерии приёмки проверены +- **Что такое «сделана»:** конвейер проекта пройден + критерии приёмки проверены поимённо ## Язык diff --git a/av-dev-docs/skills/docs/SKILL.md b/av-dev-docs/skills/docs/SKILL.md index d0fac27..648d937 100644 --- a/av-dev-docs/skills/docs/SKILL.md +++ b/av-dev-docs/skills/docs/SKILL.md @@ -9,8 +9,8 @@ description: Вести содержимое документов канона Определение канона и роли документов — [канон](../canon/references/canon.md), здесь не пересказывается. -Главный вызывающий — **шаг синка документации в пайплайне задачи**. Пайплайн -живёт в другом плагине и зовёт этот скилл по имени; проект без пайплайна ведёт +Главный вызывающий — **шаг синка документации в конвейере задачи**. Конвейер +живёт в другом плагине и зовёт этот скилл по имени; проект без конвейера ведёт документацию тем же скиллом вручную. ## Правило, из которого всё следует @@ -55,7 +55,7 @@ description: Вести содержимое документов канона - passport, security, conventions, review — не требуется: изменение внутреннее ``` -## Сверка — не здесь, а на сессии +## Сверка — не здесь, а в `av-dev-docs:healthcheck` Синк правит документы поодиночке, а расходятся они **между собой**: факт, дописанный в `architecture.md`, уже живёт в `CLAUDE.md`; периметр в @@ -80,8 +80,8 @@ description: Вести содержимое документов канона **Язык правленого вычитывается на синке, и зовёшь агента `doc-wording` ты.** Довод обратный доводу про судей: он читает **только названную пачку**, стоит дёшево и ищет ровно то, что портится в момент письма, — залог, оценку без факта, -жаргон, термин без ввода. Ждать сессии здесь нечего: через месяц никто уже не -помнит, какую фразу имел в виду автор. +жаргон, термин без ввода. Ждать `healthcheck` здесь нечего: через месяц никто уже +не помнит, какую фразу имел в виду автор. Позови его **последним шагом синка**, отдав список файлов, которых чек-лист коснулся, — и назови этот список в промпте: по нему же он судит, известен ли @@ -181,7 +181,7 @@ description: Вести содержимое документов канона Твоя часть на синке: **дефект пишется сразу**, а не «потом, когда починим». Со временем теряется не факт, а то, почему дефект не поймали, — единственное, ради чего журнал есть. И решение сузить проверки (перестали звать проход, понизили -метка) обязано попасть в раздел настройки, а не остаться в отчёте ревью. +метку) обязано попасть в раздел настройки, а не остаться в отчёте ревью. ## Промоут в конвенции diff --git a/av-dev-docs/skills/healthcheck/SKILL.md b/av-dev-docs/skills/healthcheck/SKILL.md index 15439a8..6a8acc1 100644 --- a/av-dev-docs/skills/healthcheck/SKILL.md +++ b/av-dev-docs/skills/healthcheck/SKILL.md @@ -130,9 +130,12 @@ check` и его скрипт; здесь начинается там, где к - **Не проверяет раскладку, версию и ссылки** — это `canon check`, там машина. - **Не судит язык** документов: залог, англицизмы, жаргон, термин без дома — это - агент `doc-wording`, и зовут его отдельно, по пачке правленных документов. У - него другой ритм: он нужен там, где текст только что писали, а не там, где он - год лежал. Оркестровать его нечем — он один и работает по названному списку. + агент `doc-wording`, и зовут его отдельно, по пачке правленных документов. + Звонящие у него названные — последний шаг синка в `av-dev-docs:docs`, шаг 9 + `av-dev-docs:init` и шаг вычитки в обоих режимах `canon`, — просто ни один из + них не здесь. У него другой ритм: он нужен там, где текст только что писали, а + не там, где он год лежал. Оркестровать его нечем — он один и работает по + названному списку. - **Не правит документы за агентов** — они возвращают формулировки, решение подставить принимает человек или ты по его правилу. - **Не заводит задачи** — этим владеет `av-dev-tasks:tasks`. diff --git a/av-dev-docs/skills/init/SKILL.md b/av-dev-docs/skills/init/SKILL.md index f645639..dbcf477 100644 --- a/av-dev-docs/skills/init/SKILL.md +++ b/av-dev-docs/skills/init/SKILL.md @@ -141,7 +141,7 @@ description: "Завести новый проект — сессия вопро - Содержимое канона по ходу разработки ведёт скилл `docs`. - Раскладку проверяет `canon check`. -- Первую задачу берёт пайплайн проекта; `architecture.md` и `conventions/` +- Первую задачу берёт конвейер проекта; `architecture.md` и `conventions/` наполняются его шагом синка, а не заранее. ## Чего этот скилл не делает diff --git a/av-dev-tasks/.claude-plugin/plugin.json b/av-dev-tasks/.claude-plugin/plugin.json index b143cbb..875d263 100644 --- a/av-dev-tasks/.claude-plugin/plugin.json +++ b/av-dev-tasks/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "av-dev-tasks", - "description": "Задачи и цели каталогом markdown-файлов: одна запись — файл в items/ плюс строка ровно в одном индексе, у записи тип (goal, feature, fix, chore, research), и тип решает, каких разделов она требует и что с ней можно делать. Заведение из диалога с фильтром и дедупом, разбор находок ревью в задачи, декомпозиция на независимо полезные части, гигиена полей, согласованность индексов скриптом tasks.py. Приоритет — явный порядок строк в беклоге, и расставляет его скилл groom: интерактивный разбор на два вопроса, что сейчас самое важное и что перестало быть важным. Готовность записи к работе проверяет команда ready. Записи вычитывают два прохода: task-form (форма записи) и task-wording (язык). Канон документов ведёт плагин av-dev-docs, он опционален. Задач не выполняет — этим занимается пайплайн проекта.", + "description": "Задачи и цели каталогом markdown-файлов: одна запись — файл в items/ плюс строка ровно в одном индексе, у записи тип (goal, feature, fix, chore, research), и тип решает, каких разделов она требует и что с ней можно делать. Заведение из диалога с фильтром и дедупом, разбор находок ревью в задачи, декомпозиция на независимо полезные части, гигиена полей, согласованность индексов скриптом tasks.py. Приоритет — явный порядок строк в беклоге, и расставляет его скилл groom: интерактивный разбор на два вопроса, что сейчас самое важное и что перестало быть важным. Готовность записи к работе проверяет команда ready. Записи вычитывают два прохода: task-form (форма записи) и task-wording (язык). Канон документов ведёт плагин av-dev-docs, он опционален. Задач не выполняет — этим занимается конвейер проекта.", "author": { "name": "Anton Vakhrushev", "email": "anwinged@gmail.com" diff --git a/av-dev-tasks/skills/groom/SKILL.md b/av-dev-tasks/skills/groom/SKILL.md index bceffcc..192ffe6 100644 --- a/av-dev-tasks/skills/groom/SKILL.md +++ b/av-dev-tasks/skills/groom/SKILL.md @@ -1,6 +1,6 @@ --- name: groom -description: "Груминг беклога — интерактивный разбор, отвечающий на два вопроса: что сейчас самое важное и что перестало быть важным. Ответ записывается порядком строк в беклоге: первая строка — то, что делают следующим. Разбирает накопившиеся вопросы, переоценивает задачи порциями по 5–8 (сделано попутно, отменено решением, слилось с соседней, подешевело, разрослось, стало сырьём), закрывает отжившее с причиной и расставляет очередь. Использовать, когда просят разобрать беклог, расставить приоритеты, решить «что делать дальше», провести груминг или переоценку, а также когда вернулись к проекту после перерыва и надо понять, где остановились. Формат и содержимое записей — скилл tasks; выполнение задачи — пайплайн проекта." +description: "Груминг беклога — интерактивный разбор, отвечающий на два вопроса: что сейчас самое важное и что перестало быть важным. Ответ записывается порядком строк в беклоге: первая строка — то, что делают следующим. Разбирает накопившиеся вопросы, переоценивает задачи порциями по 5–8 (сделано попутно, отменено решением, слилось с соседней, подешевело, разрослось, стало сырьём), закрывает отжившее с причиной и расставляет очередь. Использовать, когда просят разобрать беклог, расставить приоритеты, решить «что делать дальше», провести груминг или переоценку, а также когда вернулись к проекту после перерыва и надо понять, где остановились. Формат и содержимое записей — скилл tasks; выполнение задачи — конвейер проекта." --- # Груминг: что важно, что перестало @@ -22,7 +22,7 @@ description: "Груминг беклога — интерактивный ра без вопросов и показывается списком. Форматом и содержимым записей владеет скилл `tasks` — груминг зовёт его -операции, а не правит файлы руками. Выполнением задачи — пайплайн проекта. +операции, а не правит файлы руками. Выполнением задачи — конвейер проекта. ## Три правила, из которых всё следует @@ -69,7 +69,7 @@ description: "Груминг беклога — интерактивный ра **Отличать вопрос от застревания.** Правило про остаток принадлежит управлению задачами: оно решает, **сделана задача или вышла**, а это исход планирования, не -исполнения. **Ниже канонический текст; пайплайн проекта на него ссылается, а не +исполнения. **Ниже канонический текст; конвейер проекта на него ссылается, а не пересказывает** — два экземпляра одного правила разъезжаются, и разъезжаются незаметно, потому что расхождение видно только на редком входе. diff --git a/av-dev-tasks/skills/tasks/SKILL.md b/av-dev-tasks/skills/tasks/SKILL.md index 97680c2..0bb427c 100644 --- a/av-dev-tasks/skills/tasks/SKILL.md +++ b/av-dev-tasks/skills/tasks/SKILL.md @@ -11,7 +11,7 @@ description: Ведение задач и целей как каталога mar Чем он **не** владеет: **очередью** — что делать следующим и что перестало быть важным, решает скилл `groom`, а этот скилл лишь даёт ему операции; и выполнением -задачи — это пайплайн проекта. +задачи — это конвейер проекта. ## Шесть правил, из которых всё следует @@ -296,7 +296,7 @@ stateDiagram-v2 **напоминает** — беклог, заведённый до появления типа, законен, и переоформлять его «заодно» здесь не просят. -**Тип не выбирает метку ревью и вообще ничего не предписывает пайплайну.** +**Тип не выбирает метку ревью и вообще ничего не предписывает конвейеру.** Профиль выбирается по факту изменения, а не по типу задачи: `chore` бывает миграцией схемы, `fix` — правкой публичного контракта. Правило «предписание процесса в теле задачи снимается» типом не отменяется, а подтверждается: он @@ -664,7 +664,7 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап ### Вызов из другого плагина -`$CLAUDE_PLUGIN_ROOT` раскрывается **только внутри своего плагина**: пайплайн +`$CLAUDE_PLUGIN_ROOT` раскрывается **только внутри своего плагина**: конвейер задачи, конвейер ревью и любой другой чужой контекст до `tasks.py` по этой переменной не дотянутся. Мост — **вызов скилла через пространство имён**, а не путь: @@ -684,8 +684,8 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап какие в проекте оракулы — семантика гейта в `CLAUDE.md`. Отдельными слотами остаётся то, чего из раскладки не вывести. **Проект дописывает в `CLAUDE.md`**: -1. **Что такое «сделана»** — чем задача выполняется (пайплайн проекта) и что - входит в его определение готовности. Скилл требует лишь **форму**: пайплайн +1. **Что такое «сделана»** — чем задача выполняется (конвейер проекта) и что + входит в его определение сделанного. Скилл требует лишь **форму**: конвейер проекта пройден + критерии приёмки проверены поимённо. 2. **Что считается необратимым** и потому спрашивается у человека всегда (деплой, выкладка наружу, удаление или перезапись данных). @@ -713,7 +713,7 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап ## Чего этот скилл не делает Не пишет код, не заводит спеки и предложения об изменении, не берёт задачу в -работу — этим занимается пайплайн проекта. **Не ведёт очередь:** что делать +работу — этим занимается конвейер проекта. **Не ведёт очередь:** что делать следующим и что перестало быть важным — скилл `groom`, а этот даёт ему операции. Не решает за пользователя, что важно. Не переоформляет существующие задачи «заодно»: правится то, чего касается операция. diff --git a/av-dev-tasks/skills/tasks/references/task-feature.md b/av-dev-tasks/skills/tasks/references/task-feature.md index 043ff9d..d00a1ee 100644 --- a/av-dev-tasks/skills/tasks/references/task-feature.md +++ b/av-dev-tasks/skills/tasks/references/task-feature.md @@ -42,7 +42,7 @@ заходом и не мерджится целиком — это несколько задач под одной целью, дроби сразу ([split.md](split.md)). Промежуточного зонтика между целью и задачей нет. -6. **Реализация** — дело пайплайна проекта, не этого скилла. Закрывается +6. **Реализация** — дело конвейера проекта, не этого скилла. Закрывается `close <слаг> --implemented`: файл и строка удаляются, суть переезжает в `openspec/specs/` и документацию. diff --git a/av-dev-tasks/skills/tasks/references/task-format.md b/av-dev-tasks/skills/tasks/references/task-format.md index 05f6f4e..44e9e47 100644 --- a/av-dev-tasks/skills/tasks/references/task-format.md +++ b/av-dev-tasks/skills/tasks/references/task-format.md @@ -154,7 +154,7 @@ 2–5 проверяемых утверждений **списком** `- …`, **у каждого назван оракул**. Не «работает корректно», а «повторный прогон даёт тот же отпечаток — оракул: -команда сверки». Это не второе определение готовности, а проектная +команда сверки». Это не второе определение сделанного, а проектная конкретизация вопроса «по чему видно, что закончено» из теста готовности ниже: там сказано «признак завершённости», здесь — «признак плюс чем проверяется». diff --git a/lefthook.yml b/lefthook.yml index 0d2d601..26547ad 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -9,7 +9,7 @@ # # **Что судится — staged-файлы, а не рабочее дерево**, всюду, где проверка # умеет смотреть поимённо: гейт обязан судить то, что уедет в историю, а не то, -# что случайно лежит на диске рядом. Два исключения названы у своих задач, и оба +# что случайно лежит на диске рядом. Три исключения названы у своих задач, и все # — про то, что проверке нужен весь репозиторий по существу, а не для удобства. # # Ставится `lefthook install` (см. README, «Гейт коммита»). Обойти разово — @@ -23,9 +23,12 @@ pre-commit: # copies.py сверяет копию с домом, а дом лежит в другом файле, которого в # индексе может не быть: список staged дал бы «копии дословны» там, где # правка дома их и разошлась. frontmatter.py смотрел бы поимённо, но весь - # обход стоит сотые доли секунды — платить за него нечем. + # обход стоит сотые доли секунды — платить за него нечем. Glob у него шире + # на `*.json`: тем же проходом сверяется `description` плагина в + # `plugin.json` с записью того же плагина в `marketplace.json`, а коммит, + # правящий только манифест, по глобу `*.md` проверку бы не разбудил. - name: фронтматтеры - glob: "*.md" + glob: "*.{md,json}" run: python3 scripts/frontmatter.py - name: копии правил diff --git a/scripts/frontmatter.py b/scripts/frontmatter.py index 1a7ca0c..a7fc9e3 100644 --- a/scripts/frontmatter.py +++ b/scripts/frontmatter.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Проверка фронтматтеров скиллов и charter'ов этого репозитория. +"""Проверка фронтматтеров скиллов и charter'ов этого репозитория и описаний плагинов. Фронтматтер — единственная часть скилла, которую читает не человек, а загрузчик: по `name` он разрешает вызов, по `description` решает, звать ли скилл вообще. @@ -7,7 +7,7 @@ разумную строку, а скилл либо не находится по имени, либо загружается с обрезанным описанием и потому не срабатывает на своих же триггерах. -Ловится три класса. +Ловится четыре класса. **Двоеточие с пробелом в описании без кавычек.** В YAML `: ` внутри простого скаляра начинает вложенное отображение — строка «конвейер ревью: гейт, сверка…» @@ -26,8 +26,14 @@ списку агентов, и держаться вниманием оно не может: цвет ставится один раз при заведении charter'а, а модель потом меняется калибровкой. +**Описание плагина, разошедшееся между манифестами.** У описания два дома: +`<плагин>/.claude-plugin/plugin.json` его показывает установленному плагину, +корневой `.claude-plugin/marketplace.json` — тому, кто выбирает, ставить ли. +Правят обычно один, и разойтись они успели уже трижды из четырёх. `copies.py` +этот класс не берёт: он смотрит markdown, а манифест — json. + Коды выхода — тот же словарь, что у tasks.py, docs.py, copies.py и diagrams.py: - 0 все фронтматтеры в порядке + 0 все фронтматтеры и описания в порядке 1 расхождение 2 ошибка употребления: аргументы 3 окружение: не тот каталог @@ -37,6 +43,7 @@ from __future__ import annotations import argparse +import json import sys from pathlib import Path @@ -129,6 +136,40 @@ def collect(root: Path) -> list[tuple[Sheet, str, set[str]]]: return found +def manifests(root: Path) -> list[tuple[str, list[str]]]: + """Описание каждого плагина: `plugin.json` против `marketplace.json`.""" + market = root / ".claude-plugin" / "marketplace.json" + where = market.relative_to(root).as_posix() + try: + listed = { + str(entry.get("name", "")): str(entry.get("description", "")) + for entry in json.loads(market.read_text(encoding="utf-8"))["plugins"] + } + except (OSError, ValueError, KeyError, TypeError) as e: + return [(where, [f"манифест маркетплейса не разбирается: {e}"])] + + found: list[tuple[str, list[str]]] = [] + for plugin in sorted(root.glob("av-*/")): + card = plugin / ".claude-plugin" / "plugin.json" + rel = card.relative_to(root).as_posix() + try: + own = json.loads(card.read_text(encoding="utf-8")) + except (OSError, ValueError) as e: + found.append((rel, [f"манифест плагина не разбирается: {e}"])) + continue + name = str(own.get("name", plugin.name)) + if name not in listed: + found.append((rel, [ + f"плагина `{name}` нет в {where} — маркетплейс его не отдаёт" + ])) + elif str(own.get("description", "")) != listed[name]: + found.append((rel, [ + f"`description` разошлось с записью `{name}` в {where}:" + f" у описания один текст на два манифеста, и правят обычно один" + ])) + return found + + def main() -> int: ap = argparse.ArgumentParser(description="Проверка фронтматтеров.") ap.add_argument("--dir", default=".", help="корень репозитория") @@ -150,17 +191,25 @@ def main() -> int: if sheet.parsed: sheet.check(expected, required) + cards = manifests(root) + plugins = len(list(root.glob("av-*/"))) + skills = sum(1 for _, _, required in sheets if required is SKILL_KEYS) print(f"фронтматтеров {len(sheets)}: скиллов {skills}," f" charter'ов {len(sheets) - skills}") + print(f"манифестов плагинов {plugins}: описание сверено с marketplace.json") broken = [sheet for sheet, _, _ in sheets if sheet.problems] - if broken: + if broken or cards: print() for sheet in broken: for problem in sheet.problems: print(f"ОШИБКА {sheet.where}\n {problem}") - print(f"\nИтог: с ошибками {len(broken)} из {len(sheets)}.") + for rel, problems in cards: + for problem in problems: + print(f"ОШИБКА {rel}\n {problem}") + print(f"\nИтог: с ошибками {len(broken) + len(cards)}" + f" из {len(sheets) + plugins}.") return DRIFT print("все в порядке")