From 84134cac1e31b8367d750353ff8b3ddf949e4944 Mon Sep 17 00:00:00 2001 From: Anton Vakhrushev Date: Tue, 4 Aug 2026 16:49:02 +0300 Subject: [PATCH] =?UTF-8?q?=D1=80=D0=B5=D0=B2=D1=8C=D1=8E:=20=D1=81=D1=82?= =?UTF-8?q?=D1=83=D0=BF=D0=B5=D0=BD=D1=8C=20wide,=20=D1=86=D0=B2=D0=B5?= =?UTF-8?q?=D1=82=D0=B0=20=D0=BF=D0=BE=20=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D0=B8,=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D0=B0=20?= =?UTF-8?q?=D1=84=D1=80=D0=BE=D0=BD=D1=82=D0=BC=D0=B0=D1=82=D1=82=D0=B5?= =?UTF-8?q?=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Прыжок 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) --- README.md | 31 ++- av-dev-backlog/skills/backlog/SKILL.md | 2 +- av-dev-pipeline/agents/review-adversary.md | 2 +- av-dev-pipeline/agents/review-architecture.md | 2 +- av-dev-pipeline/agents/review-code.md | 4 +- av-dev-pipeline/agents/review-gate.md | 2 +- av-dev-pipeline/agents/review-ops.md | 2 +- av-dev-pipeline/agents/review-reimpl.md | 12 +- av-dev-pipeline/agents/review-rubric.md | 2 +- av-dev-pipeline/agents/review-specs.md | 2 +- av-dev-pipeline/agents/review-triage.md | 2 +- .../skills/review-pipeline/SKILL.md | 135 ++++++++----- av-dev-pipeline/skills/task-batch/SKILL.md | 2 +- av-dev-pm/skills/canon/references/canon.md | 7 +- .../skills/canon/references/skeletons.md | 5 +- av-dev-pm/skills/init/SKILL.md | 2 +- av-dev-pm/skills/session/SKILL.md | 2 +- pyproject.toml | 1 + scripts/frontmatter.py | 177 ++++++++++++++++++ 19 files changed, 320 insertions(+), 74 deletions(-) create mode 100644 scripts/frontmatter.py diff --git a/README.md b/README.md index 52cb95c..708924f 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,8 @@ - `task-batch` — несколько задач разом, каждая в своём worktree; - `review-pipeline` — конвейер ревью: гейт, сверка со спеками, враждебные постановки, эксплуатационный постмортем, независимая реализация, - архитектура, обязательный триаж. Девять агентов-проходов. + архитектура, обязательный триаж. Девять агентов-проходов, четыре ступени + стоимости: `quick`, `standard`, `wide`, `deep`. - **av-dev-git** — `commit`: сообщения в личном стиле. - **av-dev-backlog** — **устарел**, заменён `av-dev-pm`. Живёт до перевода последнего проекта; как снять с проекта — [Снятие](#снятие). @@ -229,7 +230,7 @@ claude plugin uninstall av-dev-backlog@av-dev-skills --scope project /skills//references/ что читается по ссылке из скилла /skills//scripts/ tasks.py, docs.py /agents/ charter'ы сабагентов -scripts/ проверки самого репозитория: копии, диаграммы +scripts/ проверки репозитория: копии, диаграммы, фронтматтеры 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'а, а модель + потом меняется калибровкой. + ## Проверка копий правил «Один факт — один дом» держалось вниманием и трижды не удержалось. Копии всё же diff --git a/av-dev-backlog/skills/backlog/SKILL.md b/av-dev-backlog/skills/backlog/SKILL.md index 289921f..67a59df 100644 --- a/av-dev-backlog/skills/backlog/SKILL.md +++ b/av-dev-backlog/skills/backlog/SKILL.md @@ -1,6 +1,6 @@ --- 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/` из плагина diff --git a/av-dev-pipeline/agents/review-adversary.md b/av-dev-pipeline/agents/review-adversary.md index f2b5bd0..e382011 100644 --- a/av-dev-pipeline/agents/review-adversary.md +++ b/av-dev-pipeline/agents/review-adversary.md @@ -3,7 +3,7 @@ name: review-adversary description: "Враждебный проход ревью — не проверяет свойства, а строит путь: «ты контролируешь вход целиком — выведи запись за пределы песочницы»; «ты шлёшь запрос и хочешь, чтобы данные не доехали или испортились — построй такой вход»; «ты можешь повторить и переставить любую операцию — что ломается»; «доведи чувствительное до места, где его быть не должно». Находка — построенный путь с шагами, а не наблюдение. Свойства без пути идут в отдельную секцию и не получают critical. Модель угроз берётся из docs/security.md проекта. Только чтение." tools: Read, Grep, Glob, Bash model: opus -color: red +color: yellow --- Ты — враждебный проход ревью. Разница между тобой и чек-листом безопасности diff --git a/av-dev-pipeline/agents/review-architecture.md b/av-dev-pipeline/agents/review-architecture.md index a47ac3b..baddc73 100644 --- a/av-dev-pipeline/agents/review-architecture.md +++ b/av-dev-pipeline/agents/review-architecture.md @@ -3,7 +3,7 @@ name: review-architecture description: "Архитектурный проход ревью — получает вход шире диффа (дерево пакетов, граф внутренних зависимостей, инвентарь существующих концепций). Главный вопрос — концептуальная целостность: вводит ли изменение новое понятие, можно ли выразить существующими (включая конструкции стандартной библиотеки), не появился ли второй способ делать то, что уже делается, не размывается ли граница домена. Потолок 3 находки плюс секция «дешевле переделать до мерджа». Работает и на предложении до кода (профиль design). Только чтение." tools: Read, Grep, Glob, Bash model: fable -color: yellow +color: red --- Ты — архитектурный проход ревью. Агент, видящий только дифф, физически не может diff --git a/av-dev-pipeline/agents/review-code.md b/av-dev-pipeline/agents/review-code.md index 338d13b..0ffd742 100644 --- a/av-dev-pipeline/agents/review-code.md +++ b/av-dev-pipeline/agents/review-code.md @@ -3,7 +3,7 @@ name: review-code description: "Стадия 1 конвейера ревью (во всех профилях) — дешёвый applicative-проход по прозаическим конвенциям проекта, тем, которые НЕ выражаются правилом линтера: уровень лога по адресату, единственный логирующий чекпоинт на доменной границе, трансляция ошибки на внешней границе, транзиентный ответ против персистентной диагностики, что не попадает в логи, конфиг и его образцы, канонический вид и нормализация на границах, время и идентификаторы, шаблоны и единый источник разметки, тесты на реальных данных. Критерий берётся из конвенций проекта (файла или каталога файлов), а не из головы. Механизируемое проверяет гейт, архитектуру — review-architecture. Только чтение." tools: Read, Grep, Glob, Bash model: sonnet -color: blue +color: green --- Ты — проход по **прозаическим конвенциям проекта**, стадия 1 конвейера. Твоя @@ -147,7 +147,7 @@ color: blue - архитектурные границы и второй способ делать то же самое — `review-architecture`; - стиль, дублирование, лишние слои, «я бы написал иначе» — `review-architecture` - (лишнее и второй способ) и `review-reimpl` (когда он запущен по триггеру); + (лишнее и второй способ) и `review-reimpl` (когда прогон идёт профилем `deep`); - соответствие дельта-спекам — `review-specs`. Видишь такое — не выводи находкой; максимум упомяни строкой в границах покрытия, diff --git a/av-dev-pipeline/agents/review-gate.md b/av-dev-pipeline/agents/review-gate.md index 742c9cc..02ac4fb 100644 --- a/av-dev-pipeline/agents/review-gate.md +++ b/av-dev-pipeline/agents/review-gate.md @@ -3,7 +3,7 @@ name: review-gate description: "Детерминированный гейт конвейера ревью — запускает команду гейта проекта (сборка/vet/линт/формат/тесты/флаки/гонки/покрытие изменённых строк/миграции/секреты/уязвимости) и интерпретирует вывод. Отличает новые отказы от унаследованных, находит отсутствующую верификацию (изменённые строки без покрытия, конкурентность без теста, флаки). Пока гейт красный, опиниативные проходы не запускаются. Первый проход конвейера, обязателен во всех профилях." tools: Bash, Read, Grep, Glob model: sonnet -color: red +color: green --- Ты — **гейт** конвейера ревью. Твоя ценность в том, что у тебя есть объективный diff --git a/av-dev-pipeline/agents/review-ops.md b/av-dev-pipeline/agents/review-ops.md index 745016c..dd680e6 100644 --- a/av-dev-pipeline/agents/review-ops.md +++ b/av-dev-pipeline/agents/review-ops.md @@ -3,7 +3,7 @@ name: review-ops description: "Эксплуатационный проход ревью — пишет постмортем «это упало через неделю на проде» от симптома у владельца сервиса к строке кода. Обязательные вопросы: рост объёма, деградация окружения и внешних зависимостей, повторная и одновременная операция, частичный откат при двух версиях, миграция под живым потоком, отмена контекста на середине, наблюдаемость и тишина, поведение библиотеки и драйвера в вырожденном случае, чтение узлом состояния, которое он сам же меняет. Формулирует условиями, а не утверждениями — реального профиля нагрузки не знает. Только чтение." tools: Read, Grep, Glob, Bash model: sonnet -color: yellow +color: green --- Ты — эксплуатационный проход ревью. Твоя постановка не «найди ошибки», а **«это diff --git a/av-dev-pipeline/agents/review-reimpl.md b/av-dev-pipeline/agents/review-reimpl.md index 5ac0749..612e3f2 100644 --- a/av-dev-pipeline/agents/review-reimpl.md +++ b/av-dev-pipeline/agents/review-reimpl.md @@ -1,9 +1,9 @@ --- name: review-reimpl -description: "Самый дорогой и самый ценный generative-проход ревью — получает спеку и контракты соседей, пишет собственную реализацию во временном каталоге, НЕ ОТКРЫВАЯ существующую, и только потом диффит по решениям (декомпозиция, где обрабатываются ошибки, что вынесено в интерфейс, владение данными, протяжка context, модель конкурентности). Единственный проход, который системно достаёт «не знаю, чего не знаю». Запускается по триггеру. Существующий код не меняет." +description: "Самый дорогой и самый ценный generative-проход ревью — получает спеку и контракты соседей, пишет собственную реализацию во временном каталоге, НЕ ОТКРЫВАЯ существующую, и только потом диффит по решениям (декомпозиция, где обрабатываются ошибки, что вынесено в интерфейс, владение данными, протяжка context, модель конкурентности). Единственный проход, который системно достаёт «не знаю, чего не знаю». Запускается только в профиле deep — он и есть верхняя ступень стоимости. Существующий код не меняет." tools: Read, Grep, Glob, Bash, Write model: opus -color: purple +color: yellow --- Ты — проход **независимой реализации**. Все остальные проходы смотрят на готовое @@ -37,9 +37,11 @@ color: purple именно не было. **Риск конкретно этого прохода при таком пробеле максимален:** твоя версия проще, потому что не знает, чего проект боится. -**Тебя запускают по триггеру, а не всегда.** Триггер: изменение вводит **новое -правило идентичности, слияния или разбора** (проектная формулировка — в разделе -`docs/review.md`, если он там записан). Вне его твой счёт — самый большой в +**Тебя запускают только в верхнем профиле, `deep`, а не всегда.** Он выбирается +ровно тогда, когда изменение вводит **новое правило идентичности, слияния или +разбора** (проектная формулировка — в разделе +`docs/review.md`, если он там записан); ты — единственное, чем `deep` отличается +от соседней ступени `wide`. Вне этого случая твой счёт — самый большой в конвейере (он определяется объёмом вывода: ты пишешь реализацию целиком), а независимый взгляд в значительной мере уже дал профиль `design` — код писался под его находки. Если diff --git a/av-dev-pipeline/agents/review-rubric.md b/av-dev-pipeline/agents/review-rubric.md index f00d03b..2220435 100644 --- a/av-dev-pipeline/agents/review-rubric.md +++ b/av-dev-pipeline/agents/review-rubric.md @@ -3,7 +3,7 @@ name: review-rubric description: "Generative-проход ревью — сперва, НЕ ВИДЯ КОДА, порождает 8–12 проверяемых свойств, по которым сильный инженер судит узел такого назначения (парсер входного формата, HTTP-обработчик, репозиторий, воркер, клиент внешнего сервиса, CLI-команда, файловое хранилище), и только потом читает код и оценивает по этой рубрике. Достаёт слой, которого нет ни в одной конвенции. Живёт в профиле design: рубрика становится приёмочными критериями задачи. Только чтение." tools: Read, Grep, Glob, Bash model: opus -color: purple +color: yellow --- Ты — generative-проход ревью. Чек-лист находит ровно то, что в нём перечислено; diff --git a/av-dev-pipeline/agents/review-specs.md b/av-dev-pipeline/agents/review-specs.md index 203a201..0c9a7a5 100644 --- a/av-dev-pipeline/agents/review-specs.md +++ b/av-dev-pipeline/agents/review-specs.md @@ -3,7 +3,7 @@ name: review-specs description: "Сверка изменения с дельта-спеками в обе стороны — spec→code (каждое требование реализовано и подтверждено тестом) и, что важнее, code→spec (поведение, которое код имеет, а спека не заказывала: тихие ветки, самодеятельные дефолты, проглоченные ошибки, отброшенные поля, ретраи «на всякий случай»). Плюс границы спеки — что она не определяет и что пришлось домыслить. Работает в трёх режимах: дизайн/спеки ДО кода, код против спек ПОСЛЕ apply и стык после слияния нескольких задач, когда change уже заархивированы. Только чтение." tools: Read, Grep, Glob, Bash model: opus -color: cyan +color: yellow --- Ты — ревьювер соответствия изменения его **дельта-спекам** (Spec Driven diff --git a/av-dev-pipeline/agents/review-triage.md b/av-dev-pipeline/agents/review-triage.md index f9f4375..ca439a7 100644 --- a/av-dev-pipeline/agents/review-triage.md +++ b/av-dev-pipeline/agents/review-triage.md @@ -3,7 +3,7 @@ name: review-triage description: "Обязательный финальный проход конвейера ревью — единственный, кто агрегирует. Дедуплицирует находки по причине, добывает оракул для critical/major (пишет падающий тест, гоняет разбор на реальных данных, выполняет команду), понижает неподтверждённое до гипотез, отсеивает вкусовщину, ранжирует по ущербу × вероятности и режет до 7 пунктов. Помечает каждую находку «инлайн» или «развилка» для оркестратора. Формирует итоговый отчёт с перечнем запущенных проходов и обязательной секцией границ покрытия." tools: Read, Grep, Glob, Bash, Write model: fable -color: green +color: red --- Ты — триаж конвейера ревью. Единственный проход, который видит выводы всех diff --git a/av-dev-pipeline/skills/review-pipeline/SKILL.md b/av-dev-pipeline/skills/review-pipeline/SKILL.md index 80ee52f..fcf4fbf 100644 --- a/av-dev-pipeline/skills/review-pipeline/SKILL.md +++ b/av-dev-pipeline/skills/review-pipeline/SKILL.md @@ -1,6 +1,6 @@ --- 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 каждого агента, менять её здесь не нужно. -| Модель | Проходы | Почему | -|---|---|---| -| `sonnet` | gate, code, ops | вход структурный, критерий записан заранее | -| `opus` | specs, adversary, rubric, reimpl | суждение без опоры на инструмент | -| `fable` | triage, architecture | ошибка распространяется дальше самой находки | +| Модель | Цвет | Проходы | Почему | +|---|---|---|---| +| `sonnet` | green | gate, code, ops | вход структурный, критерий записан заранее | +| `opus` | yellow | specs, adversary, rubric, reimpl | суждение без опоры на инструмент | +| `fable` | red | triage, architecture | ошибка распространяется дальше самой находки | + +**Цвет charter'а кодирует модель, а не роль прохода.** Это единственное +назначение цвета: список агентов читается взглядом, и по нему сразу видно, чем +платит прогон. Роль прохода из имени и так понятна, а цвет, розданный по ролям, +не отвечает ни на один вопрос, который задают во время прогона. Раскладка живёт +здесь и **проверяется механически** — цвет ставится один раз при заведении +charter'а, а модель потом двигает калибровка, и разъезжаются они молча. **Самая дорогая модель — только двум проходам, и это калибровка, а не осторожность.** Замер: на первом же прогоне конвейера самые ценные находки дали @@ -122,9 +129,9 @@ description: Конвейер ревью изменения — детермин - `triage` — через него проходит всё, что оркестратор реализует **молча**: ложноположительная находка становится кодом, потерянный `critical` — дефектом. Ошибка триажа дороже ошибки любого отдельного прохода. -- `architecture` — запускается редко (только `deep` и `design`), потолок в - 3 находки делает его дешёвым по выходу, а находка на предложении стоит абзаца - против переписывания на готовом коде. Дёшево × высокое плечо. +- `architecture` — запускается не на каждой задаче (`wide`, `deep` и `design`), + потолок в 3 находки делает его дешёвым по выходу, а находка на предложении + стоит абзаца против переписывания на готовом коде. Дёшево × высокое плечо. `reimpl` намеренно **не** в этом списке, хотя он самый ценный из generative: его стоимость определяется объёмом вывода (он пишет реализацию целиком), так что @@ -139,8 +146,8 @@ description: Конвейер ревью изменения — детермин Дешёвому проходу просто не осталось работы. Экономия достигается не понижением модели, а **непуском прохода**: `quick` — -четыре прохода, `deep` — семь-восемь. Правило выбора профиля и есть главный -рычаг стоимости. +четыре прохода, `deep` — восемь. Правило выбора профиля и есть главный +рычаг стоимости, и ступеней у него четыре именно поэтому. ## Профили @@ -148,10 +155,18 @@ description: Конвейер ревью изменения — детермин |---|---|---|---| | `quick` | багфикс, локальная правка, доки | 0, 1, 5 | 4 | | `standard` | новая функциональность в существующем пакете | 0, 1, 2, 5 | 6 | -| `deep` | новый пакет, изменение публичного контракта, миграция схемы, трогает инварианты проекта | 0, 1, 2, 3, 4, 5 | 7–8 | +| `wide` | новый пакет, изменение публичного контракта, миграция схемы, трогает инварианты проекта | 0, 1, 2, 4, 5 | 7 | +| `deep` | изменение вводит новое правило идентичности, слияния или разбора | 0, 1, 2, 3, 4, 5 | 8 | | `design` | **до кода**, на предложении | specs + rubric + architecture (см. ниже) | 3 | -**Состав сверяется по этой таблице до коммита.** Реестр из трёх-восьми пунктов +**`wide` назван по тому, что он добавляет: вход шире диффа.** Единственное его +отличие от `standard` — архитектурный проход, а тот и получает дерево пакетов, +граф зависимостей и инвентарь понятий вместо одного диффа. Ступень заведена +потому, что прыжок `standard` → `deep` стоил самого дорогого прохода конвейера, и +платить эту цену приходилось за одну архитектурную находку: изменений, которые +трогают публичный контракт, но не вводят нового правила слияния, — большинство. + +**Состав сверяется по этой таблице до коммита.** Реестр из трёх-восьми проходов проверяется взглядом — и это единственная защита от промаха, который уже случился: пропуск прохода **не отличим от прохода без находок** (гейт зелёный, спеки сошлись, отчёт выглядит полным), а заметить его мог бы только триаж, @@ -162,16 +177,24 @@ description: Конвейер ревью изменения — детермин Правило выбора профиля — **по факту изменения, не по ощущению важности**: -- есть миграция схемы, новый пакет, изменение публичного контракта (API, - протокол, формат на диске) или трогается правило, определяющее идентичность и - слияние данных → `deep`; +- трогается правило, определяющее **идентичность, слияние или разбор** данных → + `deep`; +- иначе есть миграция схемы, новый пакет, изменение публичного контракта (API, + протокол, формат на диске) или затронут инвариант проекта → `wide`; - иначе меняется поведение, видимое снаружи (эндпоинт, форма ответа, код ответа, формат лога) → `standard`; - иначе → `quick`. -Что именно в этом проекте считается публичным контрактом и какие пути означают -`deep`, проект может уточнить в `docs/review.md`, разделе настройки конвейера. Это -**уточнение**, а не отмена: не записано — работает список выше. +**Верхняя ступень и есть триггер независимой реализации** — раньше он был +условием *внутри* `deep`, и профиль от этого распадался на два разных прогона под +одним именем. Условие никуда не делось, оно просто переехало туда, где выбирается +профиль: изменение с новым правилом слияния — единственный случай, когда триаж +называл отсутствие `reimpl` дырой покрытия. + +Что именно в этом проекте считается публичным контрактом, какие пути означают +`wide` и что здесь считается правилом идентичности, проект может уточнить в +`docs/review.md`, разделе настройки конвейера. Это **уточнение**, а не отмена: не +записано — работает список выше. Профиль объявляется в отчёте. Понижение профиля — решение оркестратора, и оно попадает в границы покрытия строкой «профиль понижен до X, потому что …». @@ -200,23 +223,23 @@ flowchart TD code["code"] adversary["adversary
(держит машину)"] ops["ops
(держит машину)"] + architecture["architecture
(wide, deep)"] barrier{{"форма изменения выживает?"}} - reimpl["reimpl
(по триггеру)"] - architecture["architecture"] + reimpl["reimpl"] triage["triage — единственный сток"] gate -->|зелёный| specs gate -->|зелёный| code gate -->|зелёный| adversary gate -->|зелёный| ops + gate -->|"зелёный, wide и deep"| architecture adversary -. один ресурс — машина .- ops specs --> barrier code --> barrier adversary --> barrier ops --> barrier barrier -->|"deep"| reimpl - barrier -->|"deep"| architecture - barrier -->|"quick, standard: барьера нет"| triage + barrier -->|"quick, standard, wide: барьера нет"| triage reimpl --> triage architecture --> triage ``` @@ -224,7 +247,8 @@ flowchart TD Читается граф так: **всё, у чего входящие рёбра закрыты, уходит одним сообщением**. В `standard` после зелёного гейта это три узла разом — `specs`, `code` и первый из меряющей пары, — а второй меряющий идёт следом за первым. В -`quick` — `specs` и `code` разом, и сразу триаж. +`wide` к этой тройке добавляется четвёртым `architecture`. В `quick` — `specs` и +`code` разом, и сразу триаж. **Схема здесь старше прозы.** Она не иллюстрация к тексту, а сам алгоритм планировщика; проза ниже объясняет рёбра и называет их цену. Разошлись — прав @@ -269,12 +293,11 @@ flowchart TD ### Барьер стоимости — вместо раннего выхода Барьер существует ровно там, где ранний выход зарабатывал: `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` его тоже нет, и по другой причине: там предметом и является форма, а все три прохода @@ -352,7 +383,7 @@ flowchart TD Recall обоих равен длине их источника — это и есть предел applicative-проходов, ради которого существует стадия 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` — пишет свою реализацию, не открывая существующую, затем - диффит по решениям. **Запускается по триггеру, а не всегда:** изменение вводит - новое правило идентичности, слияния или разбора (проектная формулировка - триггера — в `docs/review.md`, если записана). Это самый дорогой проход конвейера - (его счёт определяется объёмом вывода — он пишет реализацию целиком), а вне - этого триггера независимый взгляд в значительной мере уже дал профиль `design`: - код писался под его находки. Триггер выбран по факту: единственный раз, когда - триаж назвал отсутствие `reimpl` дырой покрытия, — это была задача с новым - правилом слияния сущностей. + диффит по решениям. **Профиль и есть его условие:** `deep` выбирается ровно + тогда, когда изменение вводит новое правило идентичности, слияния или разбора + (проектная формулировка — в `docs/review.md`, если записана). Это самый дорогой + проход конвейера (его счёт определяется объёмом вывода — он пишет реализацию + целиком), а вне этого случая независимый взгляд в значительной мере уже дал + профиль `design`: код писался под его находки. Условие выбрано по факту: + единственный раз, когда триаж назвал отсутствие `reimpl` дырой покрытия, — это + была задача с новым правилом слияния сущностей. -## Стадия 4 — Global (`deep`, `design`) +Раньше это условие стояло **внутри** профиля, и `deep` означал то семь проходов, +то восемь. Реестр состава, который «проверяется взглядом», проверять было нечем: +у профиля не было одного правильного ответа. Теперь ступеней две — `wide` и +`deep`, — и у каждой состав ровно один. -Агент `review-architecture`. В `deep` стоит **за барьером стоимости**, в `design` -— в одном ряду с двумя другими проходами. Машину не держит, с `reimpl` конфликта -не имеет: за барьером они уходят разом. +## Стадия 4 — Global (`wide`, `deep`, `design`) + +Агент `review-architecture`. В `deep` стоит **за барьером стоимости** (ждать ему +там всё равно нечего), в `wide` и `design` — в первой волне, сразу после старта +профиля. Машину не держит, с `reimpl` конфликта не имеет: за барьером они уходят +разом. Получает **вход шире диффа**: дерево пакетов с назначением, граф внутренних зависимостей, инвентарь существующих концепций. diff --git a/av-dev-pipeline/skills/task-batch/SKILL.md b/av-dev-pipeline/skills/task-batch/SKILL.md index 03852c4..dadda18 100644 --- a/av-dev-pipeline/skills/task-batch/SKILL.md +++ b/av-dev-pipeline/skills/task-batch/SKILL.md @@ -114,7 +114,7 @@ description: Проводит несколько задач разом — пл где уже мерили или уже ломалось. Ни один триггер не сработал — задача не замеряющая, даже если её ревью - окажется `deep`. `deep` про глубину проверки, замеряющая — про соревнование за + окажется `deep`. Профиль про глубину проверки, замеряющая — про соревнование за железо; это разные вопросы, и совпадают они не всегда; - **нумерованные артефакты — номера раздаёт оркестратор заранее.** Если проект нумерует миграции (путь — `docs/.pm.json`, ключ `migrations`), посмотри последний diff --git a/av-dev-pm/skills/canon/references/canon.md b/av-dev-pm/skills/canon/references/canon.md index 7497610..f553d23 100644 --- a/av-dev-pm/skills/canon/references/canon.md +++ b/av-dev-pm/skills/canon/references/canon.md @@ -183,9 +183,10 @@ kebab-case. - **Вопросы к проходам** — поимённо, в форме `<имя прохода>: <вопрос> (<провенанс>)`; - **Триггеры профиля** — проектная конкретизация правила выбора профиля ревью: - какие пути и контракты означают `deep`, что считается «поведением, видимым - снаружи», при каком изменении запускается независимая реализация. Уточняет - умолчания конвейера, а не отменяет их; + какие пути и контракты означают `wide`, что считается «поведением, видимым + снаружи», что в этом проекте считается правилом идентичности, слияния или + разбора — оно и поднимает прогон до `deep`. Уточняет умолчания конвейера, а не + отменяет их; - **Недоступно проверке** — два подраздела: «не проверит ни один проход» (принципиальная граница, по факту промаха не пересматривается) и «перестали проверять сознательно» (пересматривается первым). diff --git a/av-dev-pm/skills/canon/references/skeletons.md b/av-dev-pm/skills/canon/references/skeletons.md index 61b74d8..0c81588 100644 --- a/av-dev-pm/skills/canon/references/skeletons.md +++ b/av-dev-pm/skills/canon/references/skeletons.md @@ -283,8 +283,9 @@ ### Триггеры профиля Проектная конкретизация правила выбора профиля: какие пути и контракты означают -`deep`, что здесь считается «поведением, видимым снаружи», при каком изменении -запускается независимая реализация. Уточняет умолчания конвейера, не отменяет их. +`wide`, что здесь считается «поведением, видимым снаружи», что здесь считается +правилом идентичности, слияния или разбора — оно поднимает прогон до `deep` и +запускает независимую реализацию. Уточняет умолчания конвейера, не отменяет их. ### Недоступно проверке diff --git a/av-dev-pm/skills/init/SKILL.md b/av-dev-pm/skills/init/SKILL.md index aa39663..103ee79 100644 --- a/av-dev-pm/skills/init/SKILL.md +++ b/av-dev-pm/skills/init/SKILL.md @@ -1,6 +1,6 @@ --- name: init -description: Завести новый проект — сессия вопросов и ответов по свободному описанию замысла, из которой рождается первичная документация по канону av-dev: паспорт, CLAUDE.md с инвариантами и командами, модель угроз с периметром, первые цели в плане и скелет остальных документов. Использовать, когда начинают новый проект с нуля, когда есть только текст «что мне нужно и почему» и надо превратить его в рабочую документацию, когда просят провести стартовое интервью по брифу. Проект, где документация уже как-то ведётся, переводит скилл canon. +description: "Завести новый проект — сессия вопросов и ответов по свободному описанию замысла, из которой рождается первичная документация по канону av-dev: паспорт, CLAUDE.md с инвариантами и командами, модель угроз с периметром, первые цели в роадмапе и скелет остальных документов. Использовать, когда начинают новый проект с нуля, когда есть только текст «что мне нужно и почему» и надо превратить его в рабочую документацию, когда просят провести стартовое интервью по брифу. Проект, где документация уже как-то ведётся, переводит скилл canon." --- # Заведение нового проекта diff --git a/av-dev-pm/skills/session/SKILL.md b/av-dev-pm/skills/session/SKILL.md index 812f181..2e05b80 100644 --- a/av-dev-pm/skills/session/SKILL.md +++ b/av-dev-pm/skills/session/SKILL.md @@ -1,6 +1,6 @@ --- name: session -description: Ритуал между спринтами и ведение самого спринта: разбор накопившихся вопросов, разбор прошедшего спринта про процесс, переоценка задач порциями, выбор цели и набор нового спринта с заморозкой. Плюс правила по ходу спринта — что врывается в замороженный набор, чем вопрос отличается от блокера, когда задача выходит из спринта, что считается сделанным и что идёт в доклад. Использовать, когда просят закрыть или начать спринт, собрать набор, разобрать вопросы, провести груминг/переоценку/ретроспективу, решить «что делать дальше» или доложить итоги. Формат и содержимое задач — скилл tasks. +description: "Ритуал между спринтами и ведение самого спринта: разбор накопившихся вопросов, разбор прошедшего спринта про процесс, переоценка задач порциями, выбор цели и набор нового спринта с заморозкой. Плюс правила по ходу спринта — что врывается в замороженный набор, чем вопрос отличается от блокера, когда задача выходит из спринта, что считается сделанным и что идёт в доклад. Использовать, когда просят закрыть или начать спринт, собрать набор, разобрать вопросы, провести груминг/переоценку/ретроспективу, решить «что делать дальше» или доложить итоги. Формат и содержимое задач — скилл tasks." --- # Сессия между спринтами diff --git a/pyproject.toml b/pyproject.toml index 7388bcc..a5d20ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,5 +63,6 @@ project-includes = [ "av-dev-pm/skills/canon/scripts/docs.py", "scripts/copies.py", "scripts/diagrams.py", + "scripts/frontmatter.py", ] python-version = "3.12" diff --git a/scripts/frontmatter.py b/scripts/frontmatter.py new file mode 100644 index 0000000..dfe2e08 --- /dev/null +++ b/scripts/frontmatter.py @@ -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)