ревью: ступень wide, цвета по модели, проверка фронтматтеров

Прыжок standard → deep стоил самого дорогого прохода конвейера, а платить
приходилось за одну архитектурную находку: изменений, которые трогают
публичный контракт, но не вводят нового правила слияния, — большинство.
Ступень wide это standard плюс architecture (вход шире диффа, отсюда имя),
семь проходов против восьми.

Заодно вычистилась давняя неровность: триггер reimpl стоял внутри deep, и
профиль означал то семь проходов, то восемь — реестр состава, который
«сверяется взглядом до коммита», проверять было нечем. Теперь условие
«новое правило идентичности, слияния или разбора» выбирает профиль, reimpl
в deep безусловен и есть единственное отличие от wide. Барьер стоимости
остался только в deep: в wide за ним стоял бы один дешёвый проход с
потолком в 3 находки, а барьер сериализует то, что могло идти разом.

Цвет charter'а теперь кодирует модель, а не роль: sonnet → green,
opus → yellow, fable → red. Роль видна из имени, стоимость прогона —
ниоткуда, а список агентов читается взглядом.

scripts/frontmatter.py ловит три класса ошибок, невидимых при чтении:
- двоеточие с пробелом в незакавыченном описании — для YAML это вложенное
  отображение, а не текст. Так было написано три описания из четырнадцати,
  и читались они правильно;
- name, разошедшееся с именем каталога скилла или файла charter'а;
- цвет, не отвечающий модели: он ставится один раз при заведении charter'а,
  а модель потом двигает калибровка.

Обе ветки проверены, коды выхода — общий словарь. Триггеры профиля в
canon.md и skeletons.md подтянуты под wide.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
av
2026-08-04 16:49:02 +03:00
co-authored by Claude Opus 5
parent 3103526de2
commit 84134cac1e
19 changed files with 320 additions and 74 deletions
+29 -2
View File
@@ -24,7 +24,8 @@
- `task-batch` — несколько задач разом, каждая в своём worktree; - `task-batch` — несколько задач разом, каждая в своём worktree;
- `review-pipeline` — конвейер ревью: гейт, сверка со спеками, враждебные - `review-pipeline` — конвейер ревью: гейт, сверка со спеками, враждебные
постановки, эксплуатационный постмортем, независимая реализация, постановки, эксплуатационный постмортем, независимая реализация,
архитектура, обязательный триаж. Девять агентов-проходов. архитектура, обязательный триаж. Девять агентов-проходов, четыре ступени
стоимости: `quick`, `standard`, `wide`, `deep`.
- **av-dev-git** — `commit`: сообщения в личном стиле. - **av-dev-git** — `commit`: сообщения в личном стиле.
- **av-dev-backlog** — **устарел**, заменён `av-dev-pm`. Живёт до перевода - **av-dev-backlog** — **устарел**, заменён `av-dev-pm`. Живёт до перевода
последнего проекта; как снять с проекта — [Снятие](#снятие). последнего проекта; как снять с проекта — [Снятие](#снятие).
@@ -229,7 +230,7 @@ claude plugin uninstall av-dev-backlog@av-dev-skills --scope project
<plugin>/skills/<skill>/references/ что читается по ссылке из скилла <plugin>/skills/<skill>/references/ что читается по ссылке из скилла
<plugin>/skills/<skill>/scripts/ tasks.py, docs.py <plugin>/skills/<skill>/scripts/ tasks.py, docs.py
<plugin>/agents/ charter'ы сабагентов <plugin>/agents/ charter'ы сабагентов
scripts/ проверки самого репозитория: копии, диаграммы scripts/ проверки репозитория: копии, диаграммы, фронтматтеры
pyproject.toml линтеры скриптов, только для этого репозитория pyproject.toml линтеры скриптов, только для этого репозитория
``` ```
@@ -255,6 +256,32 @@ uv run pyrefly check # типы
живёт до перевода последнего проекта, после чего удаляется целиком. Правки в живёт до перевода последнего проекта, после чего удаляется целиком. Правки в
замороженный код — риск без выгоды. замороженный код — риск без выгоды.
## Проверка фронтматтеров
Фронтматтер читает не человек, а загрузчик: по `name` он разрешает вызов, по
`description` решает, звать ли скилл вообще. **Ошибка здесь не выглядит
ошибкой** — тем же способом, что и в диаграммах.
```
uv run python scripts/frontmatter.py # 0 в порядке, 1 расхождение, 3 не тот каталог
```
Ловится три класса:
- **двоеточие с пробелом в описании без кавычек.** Для YAML `: ` внутри
простого скаляра начинает вложенное отображение, так что «конвейер ревью: гейт,
сверка…» — это не текст с двоеточием, а синтаксическая ошибка. Так было
написано три описания из четырнадцати, и читались они правильно;
- **`name`, разошедшееся с именем каталога скилла или файла charter'а.** Вызов
разрешается по каталогу, а сообщение о промахе говорит «нет такого скилла»,
а не «имя не то»;
- **цвет charter'а, не отвечающий его модели.** Цвет кодирует модель, а не роль
прохода — раскладка живёт в
[review-pipeline/SKILL.md](av-dev-pipeline/skills/review-pipeline/SKILL.md),
разделе «Модель по проходу», здесь только её механизация. Держаться вниманием
правило не может: цвет ставится один раз при заведении charter'а, а модель
потом меняется калибровкой.
## Проверка копий правил ## Проверка копий правил
«Один факт — один дом» держалось вниманием и трижды не удержалось. Копии всё же «Один факт — один дом» держалось вниманием и трижды не удержалось. Копии всё же
+1 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: backlog name: backlog
description: УСТАРЕЛ — используй скилл av-dev-pm:tasks. Старый формат беклога (один каталог задач, индекс README, приоритеты секциями, без целей и спринтов). Вызывать ТОЛЬКО в проекте, который на этот формат ещё не переведён, и только если прямо названо имя backlog. Во всех остальных случаях, включая любую просьбу завести задачу, идею или разобрать находки ревью, работает av-dev-pm:tasks. description: "УСТАРЕЛ — используй скилл av-dev-pm:tasks. Старый формат беклога (один каталог задач, индекс README, приоритеты секциями, без целей и спринтов). Вызывать ТОЛЬКО в проекте, который на этот формат ещё не переведён, и только если прямо названо имя backlog. Во всех остальных случаях, включая любую просьбу завести задачу, идею или разобрать находки ревью, работает av-dev-pm:tasks."
--- ---
> **Этот скилл устарел.** Формат заменён каноном `docs/tasks/` из плагина > **Этот скилл устарел.** Формат заменён каноном `docs/tasks/` из плагина
+1 -1
View File
@@ -3,7 +3,7 @@ name: review-adversary
description: "Враждебный проход ревью — не проверяет свойства, а строит путь: «ты контролируешь вход целиком — выведи запись за пределы песочницы»; «ты шлёшь запрос и хочешь, чтобы данные не доехали или испортились — построй такой вход»; «ты можешь повторить и переставить любую операцию — что ломается»; «доведи чувствительное до места, где его быть не должно». Находка — построенный путь с шагами, а не наблюдение. Свойства без пути идут в отдельную секцию и не получают critical. Модель угроз берётся из docs/security.md проекта. Только чтение." description: "Враждебный проход ревью — не проверяет свойства, а строит путь: «ты контролируешь вход целиком — выведи запись за пределы песочницы»; «ты шлёшь запрос и хочешь, чтобы данные не доехали или испортились — построй такой вход»; «ты можешь повторить и переставить любую операцию — что ломается»; «доведи чувствительное до места, где его быть не должно». Находка — построенный путь с шагами, а не наблюдение. Свойства без пути идут в отдельную секцию и не получают critical. Модель угроз берётся из docs/security.md проекта. Только чтение."
tools: Read, Grep, Glob, Bash tools: Read, Grep, Glob, Bash
model: opus model: opus
color: red color: yellow
--- ---
Ты — враждебный проход ревью. Разница между тобой и чек-листом безопасности Ты — враждебный проход ревью. Разница между тобой и чек-листом безопасности
@@ -3,7 +3,7 @@ name: review-architecture
description: "Архитектурный проход ревью — получает вход шире диффа (дерево пакетов, граф внутренних зависимостей, инвентарь существующих концепций). Главный вопрос — концептуальная целостность: вводит ли изменение новое понятие, можно ли выразить существующими (включая конструкции стандартной библиотеки), не появился ли второй способ делать то, что уже делается, не размывается ли граница домена. Потолок 3 находки плюс секция «дешевле переделать до мерджа». Работает и на предложении до кода (профиль design). Только чтение." description: "Архитектурный проход ревью — получает вход шире диффа (дерево пакетов, граф внутренних зависимостей, инвентарь существующих концепций). Главный вопрос — концептуальная целостность: вводит ли изменение новое понятие, можно ли выразить существующими (включая конструкции стандартной библиотеки), не появился ли второй способ делать то, что уже делается, не размывается ли граница домена. Потолок 3 находки плюс секция «дешевле переделать до мерджа». Работает и на предложении до кода (профиль design). Только чтение."
tools: Read, Grep, Glob, Bash tools: Read, Grep, Glob, Bash
model: fable model: fable
color: yellow color: red
--- ---
Ты — архитектурный проход ревью. Агент, видящий только дифф, физически не может Ты — архитектурный проход ревью. Агент, видящий только дифф, физически не может
+2 -2
View File
@@ -3,7 +3,7 @@ name: review-code
description: "Стадия 1 конвейера ревью (во всех профилях) — дешёвый applicative-проход по прозаическим конвенциям проекта, тем, которые НЕ выражаются правилом линтера: уровень лога по адресату, единственный логирующий чекпоинт на доменной границе, трансляция ошибки на внешней границе, транзиентный ответ против персистентной диагностики, что не попадает в логи, конфиг и его образцы, канонический вид и нормализация на границах, время и идентификаторы, шаблоны и единый источник разметки, тесты на реальных данных. Критерий берётся из конвенций проекта (файла или каталога файлов), а не из головы. Механизируемое проверяет гейт, архитектуру — review-architecture. Только чтение." description: "Стадия 1 конвейера ревью (во всех профилях) — дешёвый applicative-проход по прозаическим конвенциям проекта, тем, которые НЕ выражаются правилом линтера: уровень лога по адресату, единственный логирующий чекпоинт на доменной границе, трансляция ошибки на внешней границе, транзиентный ответ против персистентной диагностики, что не попадает в логи, конфиг и его образцы, канонический вид и нормализация на границах, время и идентификаторы, шаблоны и единый источник разметки, тесты на реальных данных. Критерий берётся из конвенций проекта (файла или каталога файлов), а не из головы. Механизируемое проверяет гейт, архитектуру — review-architecture. Только чтение."
tools: Read, Grep, Glob, Bash tools: Read, Grep, Glob, Bash
model: sonnet model: sonnet
color: blue color: green
--- ---
Ты — проход по **прозаическим конвенциям проекта**, стадия 1 конвейера. Твоя Ты — проход по **прозаическим конвенциям проекта**, стадия 1 конвейера. Твоя
@@ -147,7 +147,7 @@ color: blue
- архитектурные границы и второй способ делать то же самое — - архитектурные границы и второй способ делать то же самое —
`review-architecture`; `review-architecture`;
- стиль, дублирование, лишние слои, «я бы написал иначе» — `review-architecture` - стиль, дублирование, лишние слои, «я бы написал иначе» — `review-architecture`
(лишнее и второй способ) и `review-reimpl` (когда он запущен по триггеру); (лишнее и второй способ) и `review-reimpl` (когда прогон идёт профилем `deep`);
- соответствие дельта-спекам — `review-specs`. - соответствие дельта-спекам — `review-specs`.
Видишь такое — не выводи находкой; максимум упомяни строкой в границах покрытия, Видишь такое — не выводи находкой; максимум упомяни строкой в границах покрытия,
+1 -1
View File
@@ -3,7 +3,7 @@ name: review-gate
description: "Детерминированный гейт конвейера ревью — запускает команду гейта проекта (сборка/vet/линт/формат/тесты/флаки/гонки/покрытие изменённых строк/миграции/секреты/уязвимости) и интерпретирует вывод. Отличает новые отказы от унаследованных, находит отсутствующую верификацию (изменённые строки без покрытия, конкурентность без теста, флаки). Пока гейт красный, опиниативные проходы не запускаются. Первый проход конвейера, обязателен во всех профилях." description: "Детерминированный гейт конвейера ревью — запускает команду гейта проекта (сборка/vet/линт/формат/тесты/флаки/гонки/покрытие изменённых строк/миграции/секреты/уязвимости) и интерпретирует вывод. Отличает новые отказы от унаследованных, находит отсутствующую верификацию (изменённые строки без покрытия, конкурентность без теста, флаки). Пока гейт красный, опиниативные проходы не запускаются. Первый проход конвейера, обязателен во всех профилях."
tools: Bash, Read, Grep, Glob tools: Bash, Read, Grep, Glob
model: sonnet model: sonnet
color: red color: green
--- ---
Ты — **гейт** конвейера ревью. Твоя ценность в том, что у тебя есть объективный Ты — **гейт** конвейера ревью. Твоя ценность в том, что у тебя есть объективный
+1 -1
View File
@@ -3,7 +3,7 @@ name: review-ops
description: "Эксплуатационный проход ревью — пишет постмортем «это упало через неделю на проде» от симптома у владельца сервиса к строке кода. Обязательные вопросы: рост объёма, деградация окружения и внешних зависимостей, повторная и одновременная операция, частичный откат при двух версиях, миграция под живым потоком, отмена контекста на середине, наблюдаемость и тишина, поведение библиотеки и драйвера в вырожденном случае, чтение узлом состояния, которое он сам же меняет. Формулирует условиями, а не утверждениями — реального профиля нагрузки не знает. Только чтение." description: "Эксплуатационный проход ревью — пишет постмортем «это упало через неделю на проде» от симптома у владельца сервиса к строке кода. Обязательные вопросы: рост объёма, деградация окружения и внешних зависимостей, повторная и одновременная операция, частичный откат при двух версиях, миграция под живым потоком, отмена контекста на середине, наблюдаемость и тишина, поведение библиотеки и драйвера в вырожденном случае, чтение узлом состояния, которое он сам же меняет. Формулирует условиями, а не утверждениями — реального профиля нагрузки не знает. Только чтение."
tools: Read, Grep, Glob, Bash tools: Read, Grep, Glob, Bash
model: sonnet model: sonnet
color: yellow color: green
--- ---
Ты — эксплуатационный проход ревью. Твоя постановка не «найди ошибки», а **«это Ты — эксплуатационный проход ревью. Твоя постановка не «найди ошибки», а **«это
+7 -5
View File
@@ -1,9 +1,9 @@
--- ---
name: review-reimpl name: review-reimpl
description: "Самый дорогой и самый ценный generative-проход ревью — получает спеку и контракты соседей, пишет собственную реализацию во временном каталоге, НЕ ОТКРЫВАЯ существующую, и только потом диффит по решениям (декомпозиция, где обрабатываются ошибки, что вынесено в интерфейс, владение данными, протяжка context, модель конкурентности). Единственный проход, который системно достаёт «не знаю, чего не знаю». Запускается по триггеру. Существующий код не меняет." description: "Самый дорогой и самый ценный generative-проход ревью — получает спеку и контракты соседей, пишет собственную реализацию во временном каталоге, НЕ ОТКРЫВАЯ существующую, и только потом диффит по решениям (декомпозиция, где обрабатываются ошибки, что вынесено в интерфейс, владение данными, протяжка context, модель конкурентности). Единственный проход, который системно достаёт «не знаю, чего не знаю». Запускается только в профиле deep — он и есть верхняя ступень стоимости. Существующий код не меняет."
tools: Read, Grep, Glob, Bash, Write tools: Read, Grep, Glob, Bash, Write
model: opus model: opus
color: purple color: yellow
--- ---
Ты — проход **независимой реализации**. Все остальные проходы смотрят на готовое Ты — проход **независимой реализации**. Все остальные проходы смотрят на готовое
@@ -37,9 +37,11 @@ color: purple
именно не было. **Риск конкретно этого прохода при таком пробеле максимален:** именно не было. **Риск конкретно этого прохода при таком пробеле максимален:**
твоя версия проще, потому что не знает, чего проект боится. твоя версия проще, потому что не знает, чего проект боится.
**Тебя запускают по триггеру, а не всегда.** Триггер: изменение вводит **новое **Тебя запускают только в верхнем профиле, `deep`, а не всегда.** Он выбирается
правило идентичности, слияния или разбора** (проектная формулировка — в разделе ровно тогда, когда изменение вводит **новое правило идентичности, слияния или
`docs/review.md`, если он там записан). Вне его твой счёт — самый большой в разбора** (проектная формулировка — в разделе
`docs/review.md`, если он там записан); ты — единственное, чем `deep` отличается
от соседней ступени `wide`. Вне этого случая твой счёт — самый большой в
конвейере (он конвейере (он
определяется объёмом вывода: ты пишешь реализацию целиком), а независимый взгляд определяется объёмом вывода: ты пишешь реализацию целиком), а независимый взгляд
в значительной мере уже дал профиль `design` — код писался под его находки. Если в значительной мере уже дал профиль `design` — код писался под его находки. Если
+1 -1
View File
@@ -3,7 +3,7 @@ name: review-rubric
description: "Generative-проход ревью — сперва, НЕ ВИДЯ КОДА, порождает 8–12 проверяемых свойств, по которым сильный инженер судит узел такого назначения (парсер входного формата, HTTP-обработчик, репозиторий, воркер, клиент внешнего сервиса, CLI-команда, файловое хранилище), и только потом читает код и оценивает по этой рубрике. Достаёт слой, которого нет ни в одной конвенции. Живёт в профиле design: рубрика становится приёмочными критериями задачи. Только чтение." description: "Generative-проход ревью — сперва, НЕ ВИДЯ КОДА, порождает 8–12 проверяемых свойств, по которым сильный инженер судит узел такого назначения (парсер входного формата, HTTP-обработчик, репозиторий, воркер, клиент внешнего сервиса, CLI-команда, файловое хранилище), и только потом читает код и оценивает по этой рубрике. Достаёт слой, которого нет ни в одной конвенции. Живёт в профиле design: рубрика становится приёмочными критериями задачи. Только чтение."
tools: Read, Grep, Glob, Bash tools: Read, Grep, Glob, Bash
model: opus model: opus
color: purple color: yellow
--- ---
Ты — generative-проход ревью. Чек-лист находит ровно то, что в нём перечислено; Ты — generative-проход ревью. Чек-лист находит ровно то, что в нём перечислено;
+1 -1
View File
@@ -3,7 +3,7 @@ name: review-specs
description: "Сверка изменения с дельта-спеками в обе стороны — spec→code (каждое требование реализовано и подтверждено тестом) и, что важнее, code→spec (поведение, которое код имеет, а спека не заказывала: тихие ветки, самодеятельные дефолты, проглоченные ошибки, отброшенные поля, ретраи «на всякий случай»). Плюс границы спеки — что она не определяет и что пришлось домыслить. Работает в трёх режимах: дизайн/спеки ДО кода, код против спек ПОСЛЕ apply и стык после слияния нескольких задач, когда change уже заархивированы. Только чтение." description: "Сверка изменения с дельта-спеками в обе стороны — spec→code (каждое требование реализовано и подтверждено тестом) и, что важнее, code→spec (поведение, которое код имеет, а спека не заказывала: тихие ветки, самодеятельные дефолты, проглоченные ошибки, отброшенные поля, ретраи «на всякий случай»). Плюс границы спеки — что она не определяет и что пришлось домыслить. Работает в трёх режимах: дизайн/спеки ДО кода, код против спек ПОСЛЕ apply и стык после слияния нескольких задач, когда change уже заархивированы. Только чтение."
tools: Read, Grep, Glob, Bash tools: Read, Grep, Glob, Bash
model: opus model: opus
color: cyan color: yellow
--- ---
Ты — ревьювер соответствия изменения его **дельта-спекам** (Spec Driven Ты — ревьювер соответствия изменения его **дельта-спекам** (Spec Driven
+1 -1
View File
@@ -3,7 +3,7 @@ name: review-triage
description: "Обязательный финальный проход конвейера ревью — единственный, кто агрегирует. Дедуплицирует находки по причине, добывает оракул для critical/major (пишет падающий тест, гоняет разбор на реальных данных, выполняет команду), понижает неподтверждённое до гипотез, отсеивает вкусовщину, ранжирует по ущербу × вероятности и режет до 7 пунктов. Помечает каждую находку «инлайн» или «развилка» для оркестратора. Формирует итоговый отчёт с перечнем запущенных проходов и обязательной секцией границ покрытия." description: "Обязательный финальный проход конвейера ревью — единственный, кто агрегирует. Дедуплицирует находки по причине, добывает оракул для critical/major (пишет падающий тест, гоняет разбор на реальных данных, выполняет команду), понижает неподтверждённое до гипотез, отсеивает вкусовщину, ранжирует по ущербу × вероятности и режет до 7 пунктов. Помечает каждую находку «инлайн» или «развилка» для оркестратора. Формирует итоговый отчёт с перечнем запущенных проходов и обязательной секцией границ покрытия."
tools: Read, Grep, Glob, Bash, Write tools: Read, Grep, Glob, Bash, Write
model: fable model: fable
color: green color: red
--- ---
Ты — триаж конвейера ревью. Единственный проход, который видит выводы всех Ты — триаж конвейера ревью. Единственный проход, который видит выводы всех
+86 -49
View File
@@ -1,6 +1,6 @@
--- ---
name: review-pipeline name: review-pipeline
description: Конвейер ревью изменения — детерминированный гейт, сверка с дельта-спеками в обе стороны, враждебные постановки и эксплуатационный постмортем, независимая реализация по триггеру, архитектура и обязательный триаж. Порядок прогона — граф зависимостей, а не очередь: гейт открывает опиниативные проходы, проходы с пометкой «держит машину» идут цепочкой, дорогие generative-проходы стоят за барьером стоимости, триаж — единственный сток. Линейный прогон — по слову оператора или на занятой машине. Проектная специфика приходит из документов канона av-dev-pm. Вызывается из task-pipeline (чекпоинты ревью), из task-batch (финальная сверка) и отдельно — профилем design на предложении ДО кода. description: "Конвейер ревью изменения — детерминированный гейт, сверка с дельта-спеками в обе стороны, враждебные постановки и эксплуатационный постмортем, архитектурный проход, независимая реализация в верхнем профиле и обязательный триаж. Четыре ступени стоимости: quick, standard, wide, deep. Порядок прогона — граф зависимостей, а не очередь: гейт открывает опиниативные проходы, проходы с пометкой «держит машину» идут цепочкой, независимая реализация стоит за барьером стоимости, триаж — единственный сток. Линейный прогон — по слову оператора или на занятой машине. Проектная специфика приходит из документов канона av-dev-pm. Вызывается из task-pipeline (чекпоинты ревью), из task-batch (финальная сверка) и отдельно — профилем design на предложении ДО кода."
--- ---
# Конвейер ревью # Конвейер ревью
@@ -103,11 +103,18 @@ description: Конвейер ревью изменения — детермин
тем дешевле может быть модель; чем больше проход **порождает** критерий, тем тем дешевле может быть модель; чем больше проход **порождает** критерий, тем
дороже. Модель задана во frontmatter каждого агента, менять её здесь не нужно. дороже. Модель задана во frontmatter каждого агента, менять её здесь не нужно.
| Модель | Проходы | Почему | | Модель | Цвет | Проходы | Почему |
|---|---|---| |---|---|---|---|
| `sonnet` | gate, code, ops | вход структурный, критерий записан заранее | | `sonnet` | green | gate, code, ops | вход структурный, критерий записан заранее |
| `opus` | specs, adversary, rubric, reimpl | суждение без опоры на инструмент | | `opus` | yellow | specs, adversary, rubric, reimpl | суждение без опоры на инструмент |
| `fable` | triage, architecture | ошибка распространяется дальше самой находки | | `fable` | red | triage, architecture | ошибка распространяется дальше самой находки |
**Цвет charter'а кодирует модель, а не роль прохода.** Это единственное
назначение цвета: список агентов читается взглядом, и по нему сразу видно, чем
платит прогон. Роль прохода из имени и так понятна, а цвет, розданный по ролям,
не отвечает ни на один вопрос, который задают во время прогона. Раскладка живёт
здесь и **проверяется механически** — цвет ставится один раз при заведении
charter'а, а модель потом двигает калибровка, и разъезжаются они молча.
**Самая дорогая модель — только двум проходам, и это калибровка, а не **Самая дорогая модель — только двум проходам, и это калибровка, а не
осторожность.** Замер: на первом же прогоне конвейера самые ценные находки дали осторожность.** Замер: на первом же прогоне конвейера самые ценные находки дали
@@ -122,9 +129,9 @@ description: Конвейер ревью изменения — детермин
- `triage` — через него проходит всё, что оркестратор реализует **молча**: - `triage` — через него проходит всё, что оркестратор реализует **молча**:
ложноположительная находка становится кодом, потерянный `critical` — дефектом. ложноположительная находка становится кодом, потерянный `critical` — дефектом.
Ошибка триажа дороже ошибки любого отдельного прохода. Ошибка триажа дороже ошибки любого отдельного прохода.
- `architecture` — запускается редко (только `deep` и `design`), потолок в - `architecture` — запускается не на каждой задаче (`wide`, `deep` и `design`),
3 находки делает его дешёвым по выходу, а находка на предложении стоит абзаца потолок в 3 находки делает его дешёвым по выходу, а находка на предложении
против переписывания на готовом коде. Дёшево × высокое плечо. стоит абзаца против переписывания на готовом коде. Дёшево × высокое плечо.
`reimpl` намеренно **не** в этом списке, хотя он самый ценный из generative: его `reimpl` намеренно **не** в этом списке, хотя он самый ценный из generative: его
стоимость определяется объёмом вывода (он пишет реализацию целиком), так что стоимость определяется объёмом вывода (он пишет реализацию целиком), так что
@@ -139,8 +146,8 @@ description: Конвейер ревью изменения — детермин
Дешёвому проходу просто не осталось работы. Дешёвому проходу просто не осталось работы.
Экономия достигается не понижением модели, а **непуском прохода**: `quick` Экономия достигается не понижением модели, а **непуском прохода**: `quick`
четыре прохода, `deep`семь-восемь. Правило выбора профиля и есть главный четыре прохода, `deep` — восемь. Правило выбора профиля и есть главный
рычаг стоимости. рычаг стоимости, и ступеней у него четыре именно поэтому.
## Профили ## Профили
@@ -148,10 +155,18 @@ description: Конвейер ревью изменения — детермин
|---|---|---|---| |---|---|---|---|
| `quick` | багфикс, локальная правка, доки | 0, 1, 5 | 4 | | `quick` | багфикс, локальная правка, доки | 0, 1, 5 | 4 |
| `standard` | новая функциональность в существующем пакете | 0, 1, 2, 5 | 6 | | `standard` | новая функциональность в существующем пакете | 0, 1, 2, 5 | 6 |
| `deep` | новый пакет, изменение публичного контракта, миграция схемы, трогает инварианты проекта | 0, 1, 2, 3, 4, 5 | 78 | | `wide` | новый пакет, изменение публичного контракта, миграция схемы, трогает инварианты проекта | 0, 1, 2, 4, 5 | 7 |
| `deep` | изменение вводит новое правило идентичности, слияния или разбора | 0, 1, 2, 3, 4, 5 | 8 |
| `design` | **до кода**, на предложении | specs + rubric + architecture (см. ниже) | 3 | | `design` | **до кода**, на предложении | specs + rubric + architecture (см. ниже) | 3 |
**Состав сверяется по этой таблице до коммита.** Реестр из трёх-восьми пунктов **`wide` назван по тому, что он добавляет: вход шире диффа.** Единственное его
отличие от `standard` — архитектурный проход, а тот и получает дерево пакетов,
граф зависимостей и инвентарь понятий вместо одного диффа. Ступень заведена
потому, что прыжок `standard``deep` стоил самого дорогого прохода конвейера, и
платить эту цену приходилось за одну архитектурную находку: изменений, которые
трогают публичный контракт, но не вводят нового правила слияния, — большинство.
**Состав сверяется по этой таблице до коммита.** Реестр из трёх-восьми проходов
проверяется взглядом — и это единственная защита от промаха, который уже проверяется взглядом — и это единственная защита от промаха, который уже
случился: пропуск прохода **не отличим от прохода без находок** (гейт зелёный, случился: пропуск прохода **не отличим от прохода без находок** (гейт зелёный,
спеки сошлись, отчёт выглядит полным), а заметить его мог бы только триаж, спеки сошлись, отчёт выглядит полным), а заметить его мог бы только триаж,
@@ -162,16 +177,24 @@ description: Конвейер ревью изменения — детермин
Правило выбора профиля — **по факту изменения, не по ощущению важности**: Правило выбора профиля — **по факту изменения, не по ощущению важности**:
- есть миграция схемы, новый пакет, изменение публичного контракта (API, - трогается правило, определяющее **идентичность, слияние или разбор** данных →
протокол, формат на диске) или трогается правило, определяющее идентичность и `deep`;
слияние данных → `deep`; - иначе есть миграция схемы, новый пакет, изменение публичного контракта (API,
протокол, формат на диске) или затронут инвариант проекта → `wide`;
- иначе меняется поведение, видимое снаружи (эндпоинт, форма ответа, код ответа, - иначе меняется поведение, видимое снаружи (эндпоинт, форма ответа, код ответа,
формат лога) → `standard`; формат лога) → `standard`;
- иначе → `quick`. - иначе → `quick`.
Что именно в этом проекте считается публичным контрактом и какие пути означают **Верхняя ступень и есть триггер независимой реализации** — раньше он был
`deep`, проект может уточнить в `docs/review.md`, разделе настройки конвейера. Это условием *внутри* `deep`, и профиль от этого распадался на два разных прогона под
**уточнение**, а не отмена: не записано — работает список выше. одним именем. Условие никуда не делось, оно просто переехало туда, где выбирается
профиль: изменение с новым правилом слияния — единственный случай, когда триаж
называл отсутствие `reimpl` дырой покрытия.
Что именно в этом проекте считается публичным контрактом, какие пути означают
`wide` и что здесь считается правилом идентичности, проект может уточнить в
`docs/review.md`, разделе настройки конвейера. Это **уточнение**, а не отмена: не
записано — работает список выше.
Профиль объявляется в отчёте. Понижение профиля — решение оркестратора, и оно Профиль объявляется в отчёте. Понижение профиля — решение оркестратора, и оно
попадает в границы покрытия строкой «профиль понижен до X, потому что …». попадает в границы покрытия строкой «профиль понижен до X, потому что …».
@@ -200,23 +223,23 @@ flowchart TD
code["code"] code["code"]
adversary["adversary<br/>(держит машину)"] adversary["adversary<br/>(держит машину)"]
ops["ops<br/>(держит машину)"] ops["ops<br/>(держит машину)"]
architecture["architecture<br/>(wide, deep)"]
barrier{{"форма изменения выживает?"}} barrier{{"форма изменения выживает?"}}
reimpl["reimpl<br/>(по триггеру)"] reimpl["reimpl"]
architecture["architecture"]
triage["triage — единственный сток"] triage["triage — единственный сток"]
gate -->|зелёный| specs gate -->|зелёный| specs
gate -->|зелёный| code gate -->|зелёный| code
gate -->|зелёный| adversary gate -->|зелёный| adversary
gate -->|зелёный| ops gate -->|зелёный| ops
gate -->|"зелёный, wide и deep"| architecture
adversary -. один ресурс — машина .- ops adversary -. один ресурс — машина .- ops
specs --> barrier specs --> barrier
code --> barrier code --> barrier
adversary --> barrier adversary --> barrier
ops --> barrier ops --> barrier
barrier -->|"deep"| reimpl barrier -->|"deep"| reimpl
barrier -->|"deep"| architecture barrier -->|"quick, standard, wide: барьера нет"| triage
barrier -->|"quick, standard: барьера нет"| triage
reimpl --> triage reimpl --> triage
architecture --> triage architecture --> triage
``` ```
@@ -224,7 +247,8 @@ flowchart TD
Читается граф так: **всё, у чего входящие рёбра закрыты, уходит одним Читается граф так: **всё, у чего входящие рёбра закрыты, уходит одним
сообщением**. В `standard` после зелёного гейта это три узла разом — `specs`, сообщением**. В `standard` после зелёного гейта это три узла разом — `specs`,
`code` и первый из меряющей пары, — а второй меряющий идёт следом за первым. В `code` и первый из меряющей пары, — а второй меряющий идёт следом за первым. В
`quick``specs` и `code` разом, и сразу триаж. `wide` к этой тройке добавляется четвёртым `architecture`. В `quick``specs` и
`code` разом, и сразу триаж.
**Схема здесь старше прозы.** Она не иллюстрация к тексту, а сам алгоритм **Схема здесь старше прозы.** Она не иллюстрация к тексту, а сам алгоритм
планировщика; проза ниже объясняет рёбра и называет их цену. Разошлись — прав планировщика; проза ниже объясняет рёбра и называет их цену. Разошлись — прав
@@ -269,12 +293,11 @@ flowchart TD
### Барьер стоимости — вместо раннего выхода ### Барьер стоимости — вместо раннего выхода
Барьер существует ровно там, где ранний выход зарабатывал: `reimpl` пишет Барьер существует ровно там, где ранний выход зарабатывал: `reimpl` пишет
реализацию целиком и потому самый дорогой проход конвейера, `architecture` реализацию целиком и потому самый дорогой проход конвейера. Если дешёвая часть
смотрит вход шире диффа. Если дешёвая часть нашла, что **форму изменения** надо нашла, что **форму изменения** надо переделывать, он будет писать её против кода,
переделывать, оба будут читать код, которого через час не станет. которого через час не станет.
- **прошло без находок «переделать форму»** — барьер открыт, дорогие проходы - **прошло без находок «переделать форму»** — барьер открыт, `reimpl` уходит;
уходят разом;
- **есть такая находка** — прогон останавливается, находка чинится, конвейер - **есть такая находка** — прогон останавливается, находка чинится, конвейер
запускается **заново с нулевой стадии**, а не «доезжает» остатком по старому запускается **заново с нулевой стадии**, а не «доезжает» остатком по старому
коду. Незапущенные проходы идут в границы покрытия строкой «не запускался: коду. Незапущенные проходы идут в границы покрытия строкой «не запускался:
@@ -285,8 +308,16 @@ flowchart TD
барьер не срабатывает: дешевле дособрать все находки и починить пачкой, чем барьер не срабатывает: дешевле дособрать все находки и починить пачкой, чем
гонять конвейер дважды. гонять конвейер дважды.
В `quick` и `standard` барьера нет — за ним нечего защищать: стадий 3–4 в этих **`architecture` стоит за барьером только там, где барьер и так есть.** В `deep`
профилях не бывает, и граф там плоский от гейта до триажа. Находка «переделать он уходит вместе с `reimpl` — ждать ему всё равно нечего. В `wide` он стартует
сразу после зелёного гейта, в одном ряду со стадиями 1 и 2: своего барьера он не
заслуживает. Потолок в 3 находки делает его дешёвым, а барьер не бесплатен — он
сериализует то, что могло идти разом, и платить сериализацией за один дешёвый
проход не за что. Есть и вторая причина, помельче: барьер спрашивает «выживает ли
форма изменения», а `architecture` — как раз тот, кто на этот вопрос отвечает.
В `quick`, `standard` и `wide` барьера нет — за ним нечего защищать: стадии 3 в
этих профилях не бывает, и граф там плоский от гейта до триажа. Находка «переделать
форму» ловится в них триажем, а прогон после починки повторяется целиком: платить форму» ловится в них триажем, а прогон после починки повторяется целиком: платить
за это нечем, дорогих проходов в этих профилях нет. В `design` его тоже за это нечем, дорогих проходов в этих профилях нет. В `design` его тоже
нет, и по другой причине: там предметом и является форма, а все три прохода нет, и по другой причине: там предметом и является форма, а все три прохода
@@ -352,7 +383,7 @@ flowchart TD
Recall обоих равен длине их источника — это и есть предел applicative-проходов, Recall обоих равен длине их источника — это и есть предел applicative-проходов,
ради которого существует стадия 2. ради которого существует стадия 2.
## Стадия 2 — Adversarial и operational (`standard`, `deep`) ## Стадия 2 — Adversarial и operational (`standard`, `wide`, `deep`)
Два прохода: Два прохода:
@@ -368,8 +399,8 @@ Recall обоих равен длине их источника — это и е
её только прямое слово оператора про эту пару, и тогда в границы покрытия идёт её только прямое слово оператора про эту пару, и тогда в границы покрытия идёт
строка, что числа прогона сняты под соседней нагрузкой. строка, что числа прогона сняты под соседней нагрузкой.
**Эта стадия зарабатывает больше всех остальных вместе, и потому стоит в **Эта стадия зарабатывает больше всех остальных вместе, и потому стоит уже в
`standard`, а не только в `deep`.** Измерено на пяти задачах подряд: враждебный `standard`, а не только в верхних профилях.** Измерено на пяти задачах подряд: враждебный
проход дал пять из семи выживших находок дозапуска (включая обе верхние); проход дал пять из семи выживших находок дозапуска (включая обе верхние);
эксплуатационный — единственный, кто нашёл, что откат бинаря поверх новой схемы эксплуатационный — единственный, кто нашёл, что откат бинаря поверх новой схемы
стартует молча. Оба несут внешний оракул по построению: один обязан путь стартует молча. Оба несут внешний оракул по построению: один обязан путь
@@ -382,26 +413,32 @@ Recall обоих равен длине их источника — это и е
раздел «Сшивать обязаны проходы». Без этих документов стадия вырождается в общие раздел «Сшивать обязаны проходы». Без этих документов стадия вырождается в общие
места. места.
## Стадия 3 — Independent reimplementation (`deep`, по триггеру) ## Стадия 3 — Independent reimplementation (только `deep`)
Стоит **за барьером стоимости** вместе со стадией 4 — она ради этих двух проходов Единственный проход, ради которого существует **барьер стоимости**, и
и существует. единственное, что отличает `deep` от `wide`.
- `review-reimpl` — пишет свою реализацию, не открывая существующую, затем - `review-reimpl` — пишет свою реализацию, не открывая существующую, затем
диффит по решениям. **Запускается по триггеру, а не всегда:** изменение вводит диффит по решениям. **Профиль и есть его условие:** `deep` выбирается ровно
новое правило идентичности, слияния или разбора (проектная формулировка тогда, когда изменение вводит новое правило идентичности, слияния или разбора
триггера — в `docs/review.md`, если записана). Это самый дорогой проход конвейера (проектная формулировка — в `docs/review.md`, если записана). Это самый дорогой
(его счёт определяется объёмом вывода — он пишет реализацию целиком), а вне проход конвейера (его счёт определяется объёмом вывода — он пишет реализацию
этого триггера независимый взгляд в значительной мере уже дал профиль `design`: целиком), а вне этого случая независимый взгляд в значительной мере уже дал
код писался под его находки. Триггер выбран по факту: единственный раз, когда профиль `design`: код писался под его находки. Условие выбрано по факту:
триаж назвал отсутствие `reimpl` дырой покрытия, — это была задача с новым единственный раз, когда триаж назвал отсутствие `reimpl` дырой покрытия, — это
правилом слияния сущностей. была задача с новым правилом слияния сущностей.
## Стадия 4 — Global (`deep`, `design`) Раньше это условие стояло **внутри** профиля, и `deep` означал то семь проходов,
то восемь. Реестр состава, который «проверяется взглядом», проверять было нечем:
у профиля не было одного правильного ответа. Теперь ступеней две — `wide` и
`deep`, — и у каждой состав ровно один.
Агент `review-architecture`. В `deep` стоит **за барьером стоимости**, в `design` ## Стадия 4 — Global (`wide`, `deep`, `design`)
— в одном ряду с двумя другими проходами. Машину не держит, с `reimpl` конфликта
не имеет: за барьером они уходят разом. Агент `review-architecture`. В `deep` стоит **за барьером стоимости** (ждать ему
там всё равно нечего), в `wide` и `design` — в первой волне, сразу после старта
профиля. Машину не держит, с `reimpl` конфликта не имеет: за барьером они уходят
разом.
Получает **вход шире диффа**: дерево пакетов с Получает **вход шире диффа**: дерево пакетов с
назначением, граф внутренних зависимостей, инвентарь существующих концепций. назначением, граф внутренних зависимостей, инвентарь существующих концепций.
+1 -1
View File
@@ -114,7 +114,7 @@ description: Проводит несколько задач разом — пл
где уже мерили или уже ломалось. где уже мерили или уже ломалось.
Ни один триггер не сработал — задача не замеряющая, даже если её ревью Ни один триггер не сработал — задача не замеряющая, даже если её ревью
окажется `deep`. `deep` про глубину проверки, замеряющая — про соревнование за окажется `deep`. Профиль про глубину проверки, замеряющая — про соревнование за
железо; это разные вопросы, и совпадают они не всегда; железо; это разные вопросы, и совпадают они не всегда;
- **нумерованные артефакты — номера раздаёт оркестратор заранее.** Если проект - **нумерованные артефакты — номера раздаёт оркестратор заранее.** Если проект
нумерует миграции (путь — `docs/.pm.json`, ключ `migrations`), посмотри последний нумерует миграции (путь — `docs/.pm.json`, ключ `migrations`), посмотри последний
+4 -3
View File
@@ -183,9 +183,10 @@ kebab-case.
- **Вопросы к проходам** — поимённо, в форме `<имя прохода>: <вопрос> - **Вопросы к проходам** — поимённо, в форме `<имя прохода>: <вопрос>
(<провенанс>)`; (<провенанс>)`;
- **Триггеры профиля** — проектная конкретизация правила выбора профиля ревью: - **Триггеры профиля** — проектная конкретизация правила выбора профиля ревью:
какие пути и контракты означают `deep`, что считается «поведением, видимым какие пути и контракты означают `wide`, что считается «поведением, видимым
снаружи», при каком изменении запускается независимая реализация. Уточняет снаружи», что в этом проекте считается правилом идентичности, слияния или
умолчания конвейера, а не отменяет их; разбора — оно и поднимает прогон до `deep`. Уточняет умолчания конвейера, а не
отменяет их;
- **Недоступно проверке** — два подраздела: «не проверит ни один проход» - **Недоступно проверке** — два подраздела: «не проверит ни один проход»
(принципиальная граница, по факту промаха не пересматривается) и «перестали (принципиальная граница, по факту промаха не пересматривается) и «перестали
проверять сознательно» (пересматривается первым). проверять сознательно» (пересматривается первым).
@@ -283,8 +283,9 @@
### Триггеры профиля ### Триггеры профиля
Проектная конкретизация правила выбора профиля: какие пути и контракты означают Проектная конкретизация правила выбора профиля: какие пути и контракты означают
`deep`, что здесь считается «поведением, видимым снаружи», при каком изменении `wide`, что здесь считается «поведением, видимым снаружи», что здесь считается
запускается независимая реализация. Уточняет умолчания конвейера, не отменяет их. правилом идентичности, слияния или разбора — оно поднимает прогон до `deep` и
запускает независимую реализацию. Уточняет умолчания конвейера, не отменяет их.
### Недоступно проверке ### Недоступно проверке
+1 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: init name: init
description: Завести новый проект — сессия вопросов и ответов по свободному описанию замысла, из которой рождается первичная документация по канону av-dev: паспорт, CLAUDE.md с инвариантами и командами, модель угроз с периметром, первые цели в плане и скелет остальных документов. Использовать, когда начинают новый проект с нуля, когда есть только текст «что мне нужно и почему» и надо превратить его в рабочую документацию, когда просят провести стартовое интервью по брифу. Проект, где документация уже как-то ведётся, переводит скилл canon. description: "Завести новый проект — сессия вопросов и ответов по свободному описанию замысла, из которой рождается первичная документация по канону av-dev: паспорт, CLAUDE.md с инвариантами и командами, модель угроз с периметром, первые цели в роадмапе и скелет остальных документов. Использовать, когда начинают новый проект с нуля, когда есть только текст «что мне нужно и почему» и надо превратить его в рабочую документацию, когда просят провести стартовое интервью по брифу. Проект, где документация уже как-то ведётся, переводит скилл canon."
--- ---
# Заведение нового проекта # Заведение нового проекта
+1 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: session name: session
description: Ритуал между спринтами и ведение самого спринта: разбор накопившихся вопросов, разбор прошедшего спринта про процесс, переоценка задач порциями, выбор цели и набор нового спринта с заморозкой. Плюс правила по ходу спринта — что врывается в замороженный набор, чем вопрос отличается от блокера, когда задача выходит из спринта, что считается сделанным и что идёт в доклад. Использовать, когда просят закрыть или начать спринт, собрать набор, разобрать вопросы, провести груминг/переоценку/ретроспективу, решить «что делать дальше» или доложить итоги. Формат и содержимое задач — скилл tasks. description: "Ритуал между спринтами и ведение самого спринта: разбор накопившихся вопросов, разбор прошедшего спринта про процесс, переоценка задач порциями, выбор цели и набор нового спринта с заморозкой. Плюс правила по ходу спринта — что врывается в замороженный набор, чем вопрос отличается от блокера, когда задача выходит из спринта, что считается сделанным и что идёт в доклад. Использовать, когда просят закрыть или начать спринт, собрать набор, разобрать вопросы, провести груминг/переоценку/ретроспективу, решить «что делать дальше» или доложить итоги. Формат и содержимое задач — скилл tasks."
--- ---
# Сессия между спринтами # Сессия между спринтами
+1
View File
@@ -63,5 +63,6 @@ project-includes = [
"av-dev-pm/skills/canon/scripts/docs.py", "av-dev-pm/skills/canon/scripts/docs.py",
"scripts/copies.py", "scripts/copies.py",
"scripts/diagrams.py", "scripts/diagrams.py",
"scripts/frontmatter.py",
] ]
python-version = "3.12" python-version = "3.12"
+177
View File
@@ -0,0 +1,177 @@
#!/usr/bin/env python3
"""Проверка фронтматтеров скиллов и charter'ов этого репозитория.
Фронтматтер — единственная часть скилла, которую читает не человек, а загрузчик:
по `name` он разрешает вызов, по `description` решает, звать ли скилл вообще.
Ошибка здесь не выглядит ошибкой. Текст остаётся читаемым, `git diff` показывает
разумную строку, а скилл либо не находится по имени, либо загружается с
обрезанным описанием и потому не срабатывает на своих же триггерах.
Ловится три класса.
**Двоеточие с пробелом в описании без кавычек.** В YAML `: ` внутри простого
скаляра начинает вложенное отображение — строка «конвейер ревью: гейт, сверка…»
это не текст с двоеточием, а синтаксическая ошибка. Так были написаны три
описания из четырнадцати; заметить это чтением нельзя, потому что читается оно
правильно.
**Имя, разошедшееся с каталогом.** Скилл зовётся по имени каталога, а `name`
внутри — то, чем он представляется. Разъехались — вызов не разрешается, и
сообщение об этом говорит «нет такого скилла», а не «имя не то».
**Цвет, не отвечающий модели.** Цвет charter'а кодирует **модель**, на которой
идёт проход, а не его роль: раскладка — в
`av-dev-pipeline/skills/review-pipeline/SKILL.md`, раздел «Модель по проходу».
Правило существует ровно затем, чтобы стоимость прогона читалась взглядом по
списку агентов, и держаться вниманием оно не может: цвет ставится один раз при
заведении charter'а, а модель потом меняется калибровкой.
Коды выхода — тот же словарь, что у tasks.py, docs.py, copies.py и diagrams.py:
0 все фронтматтеры в порядке
1 расхождение
2 ошибка употребления: аргументы
3 окружение: не тот каталог
4 внутренний сбой
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
OK, DRIFT, USAGE, ENV, INTERNAL = 0, 1, 2, 3, 4
# Дом раскладки — «Модель по проходу» в review-pipeline/SKILL.md; здесь её
# механизация. Порядок цветов — порядок стоимости прогона.
PALETTE = {"sonnet": "green", "opus": "yellow", "fable": "red"}
SKILL_KEYS = {"name", "description"}
AGENT_KEYS = {"name", "description", "tools", "model", "color"}
class Sheet:
"""Разобранный фронтматтер одного файла."""
def __init__(self, path: Path, root: Path) -> None:
self.path = path
self.where = path.relative_to(root).as_posix()
self.fields: dict[str, str] = {}
self.problems: list[str] = []
# Разбор дошёл до полей. Ложь — фронтматтера нет вовсе, и спрашивать с
# него имя, набор полей и цвет бессмысленно: ответ будет один и тот же.
self.parsed = False
self._parse()
def _parse(self) -> None:
lines = self.path.read_text(encoding="utf-8").splitlines()
if not lines or lines[0].strip() != "---":
self.problems.append("нет фронтматтера: первая строка не `---`")
return
try:
end = lines.index("---", 1)
except ValueError:
self.problems.append("фронтматтер не закрыт строкой `---`")
return
self.parsed = True
for number, line in enumerate(lines[1:end], start=2):
if not line.strip():
continue
key, sep, value = line.partition(":")
if not sep or not key or key != key.strip():
self.problems.append(f"строка {number}: не `ключ: значение`")
continue
value = value.strip()
self.fields[key] = value
if value[:1] in ('"', "'"):
continue
if ": " in value:
self.problems.append(
f"строка {number}: у `{key}` двоеточие с пробелом в значении"
f" без кавычек — для YAML это вложенное отображение,"
f" а не текст. Обернуть значение в двойные кавычки"
)
def check(self, expected_name: str, required: set[str]) -> None:
missing = sorted(required - self.fields.keys())
if missing:
self.problems.append(f"нет обязательных полей: {', '.join(missing)}")
name = self.fields.get("name", "").strip("\"'")
if name and name != expected_name:
self.problems.append(
f"`name: {name}` разошлось с ожидаемым `{expected_name}`"
f" — вызов разрешается по второму"
)
model = self.fields.get("model", "").strip("\"'")
color = self.fields.get("color", "").strip("\"'")
if model and color:
if model not in PALETTE:
self.problems.append(
f"модель `{model}` не в раскладке цветов"
f" ({', '.join(sorted(PALETTE))}) — назначить ей цвет"
f" в «Модель по проходу» и здесь"
)
elif color != PALETTE[model]:
self.problems.append(
f"цвет `{color}` не отвечает модели `{model}`:"
f" по раскладке — `{PALETTE[model]}`"
)
def collect(root: Path) -> list[tuple[Sheet, str, set[str]]]:
"""Все фронтматтеры репозитория: лист, ожидаемое имя, обязательные поля."""
found: list[tuple[Sheet, str, set[str]]] = []
for plugin in sorted(root.glob("av-*/")):
for skill in sorted(plugin.glob("skills/*/SKILL.md")):
found.append((Sheet(skill, root), skill.parent.name, SKILL_KEYS))
for agent in sorted(plugin.glob("agents/*.md")):
found.append((Sheet(agent, root), agent.stem, AGENT_KEYS))
return found
def main() -> int:
ap = argparse.ArgumentParser(description="Проверка фронтматтеров.")
ap.add_argument("--dir", default=".", help="корень репозитория")
args = ap.parse_args()
root = Path(args.dir).resolve()
if not (root / ".claude-plugin").is_dir():
print(f"окружение: {root} не похож на корень репозитория"
f" (нет .claude-plugin)", file=sys.stderr)
return ENV
sheets = collect(root)
if not sheets:
print("окружение: не нашлось ни одного SKILL.md или charter'а",
file=sys.stderr)
return ENV
for sheet, expected, required in sheets:
if sheet.parsed:
sheet.check(expected, required)
skills = sum(1 for _, _, required in sheets if required is SKILL_KEYS)
print(f"фронтматтеров {len(sheets)}: скиллов {skills},"
f" charter'ов {len(sheets) - skills}")
broken = [sheet for sheet, _, _ in sheets if sheet.problems]
if broken:
print()
for sheet in broken:
for problem in sheet.problems:
print(f"ОШИБКА {sheet.where}\n {problem}")
print(f"\nИтог: с ошибками {len(broken)} из {len(sheets)}.")
return DRIFT
print("все в порядке")
return OK
if __name__ == "__main__":
try:
sys.exit(main())
except KeyboardInterrupt:
sys.exit(INTERNAL)
except Exception as e: # noqa: BLE001 — последний рубеж, код 4 по словарю
print(f"внутренний сбой ({type(e).__name__}): {e}", file=sys.stderr)
sys.exit(INTERNAL)