--- name: review-pipeline description: Конвейер ревью изменения — детерминированный гейт, сверка с дельта-спеками в обе стороны, враждебные постановки и эксплуатационный постмортем, независимая реализация по триггеру, архитектура и обязательный триаж. Проходы гонятся последовательно; параллельно — только по явной просьбе и с явно названным набором. Проектная специфика приходит из документов канона av-dev-pm. Вызывается из task-pipeline (чекпоинты ревью), из task-batch (финальная сверка) и отдельно — профилем design на предложении ДО кода. --- # Конвейер ревью Готовит ревью — **не заменяет его**. Потребитель отчёта — оркестратор, который чинит код; человек читает только сводку, развилки и границы покрытия. ## Три правила, из которых всё следует Если ситуация не покрыта инструкцией — решай по ним. 1. **Recall чек-листа равен длине чек-листа.** Проход, устроенный как «проверь пункты 1..N», найдёт ровно перечисленное. Всё неявное — идиомы, форма решения, «так не делают» — неперечислимо по определению: перечислимое уже стало бы конвенцией. Отсюда деление проходов на **applicative** (применяют заданный критерий) и **generative** (сперва порождают критерий или альтернативу, потом сравнивают). Расширять чек-листы бесполезно; неявный слой достают только generative-проходы. 2. **Ценность верификатора = наличие внешнего оракула × декорреляция с автором**, а не число ролей. Под всеми ролями одна модель с одними априорными, вход у всех общий: седьмая роль почти не добавляет recall, но линейно удорожает триаж. Иерархия надёжности: детерминированный инструмент > агент, который его **запускает** и интерпретирует вывод > агент с чистым мнением. Максимум работы переносим вниз. 3. **Отчёт без границ покрытия хуже отсутствия отчёта.** «Критичных проблем не обнаружено» потребляет ощущение проверенности, ничего не гарантируя. Секция границ покрытия обязательна и не сокращается — в том числе в докладе человеку. ## Предпосылки Конвейер опирается на внешнюю обвязку и без неё работает не целиком. Проверь это один раз, при установке плагина в проект: - **OpenSpec — жёсткая предпосылка, а не опция.** Профиль `design`, проход `review-specs` и вызывающий пайплайн задачи завязаны на дельта-спеки (`openspec/changes//specs/*/spec.md`), на актуальные спеки (`openspec/specs/`) и на `openspec validate --strict`. В проекте без OpenSpec шаги, зовущие `opsx:explore` / `opsx:propose` / `opsx:apply` / `opsx:archive`, упадут на «нет такого скилла», а `review-specs` останется без источника требований. **Проект без OpenSpec этим конвейером не проверяется** — подключай OpenSpec, а не понижай прогон: ветка деградации здесь не пишется, потому что непроверенная ветка деградации хуже честного отказа. - **Документы канона** — см. следующий раздел. - **Проектные копии этих скиллов и агентов удаляются при установке.** Если в проекте уже лежат свои `.claude/skills/review-pipeline`, `.claude/skills/task-pipeline`, `.claude/skills/task-batch` или `.claude/agents/<проект>-review-*.md` — снеси их. Иначе короткое имя разрешится в устаревшую проектную копию, молча и без признаков подмены. По той же причине **скиллы этого плагина зовутся с пространством имён**: `av-dev-pipeline:review-pipeline`, `av-dev-pipeline:task-pipeline`, `av-dev-pipeline:task-batch`. ## Что конвейер защищает — приходит из документов проекта Проходы общие, а нарушать нельзя проектное. Инварианты, команду гейта, объёмы, прецеденты и модель угроз конвейер **не знает** — он читает их в документах канона `av-dev-pm`, **напрямую и по жёстким путям**. Отдельного файла-брифа нет: пути известны, посредник не нужен, а второй дом для тех же фактов разошёлся бы и выглядел актуальным. Карта «что нужно проходу → где лежит» — [references/project-facts.md](references/project-facts.md). Прочитай её до раздачи заданий; там же таблица поразрядной деградации. **Деградация поразрядная, а не всё-или-ничего.** Документа нет — деградирует то, что из него читалось, и только оно: нет `docs/security.md` — слабеет `adversary`; нет `docs/research/` — числа неизвестны трём проходам; нет инвариантов в `CLAUDE.md` — `critical` по основанию «нарушен инвариант проекта» не присваивается никем. Каждый проход пишет **свою** строку в границы покрытия, с **причиной**; триаж сводит их и не сливает в одну. **Документов канона нет вовсе** — проект не приведён к канону. Скажи это строкой и предложи скилл `av-dev-pm:canon`: одна операция на проект против деградации на каждой задаче. Прогон при этом не останавливается. ## Что получает каждый проход Задание любому проходу состоит из шести вещей: - **его блок вопросов** из «Вопросы к проходам» в `docs/review.md`, если он там есть, — **дословно**. Блок адресован проходу поимённо и выведен из промаха этого проекта; заставлять девять charter'ов самим ходить за ним значит получить, что за ним ходят двое. Проход отвечает на такие вопросы явно, дополнительно к обязательным; - **контракт находок** — путь к [references/finding-contract.md](references/finding-contract.md) (в установленном плагине — `${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/`); - **изменение** — идентификатор change и путь к его дельта-спекам; - **база диффа**; - **профиль и режим** прогона — чтобы проход знал, что писать в границы покрытия; - **сужение**, если оно есть: конкретный узел, конкретная capability. Чего проход **не** получает ни в каком режиме — выводов других проходов. См. «Режим запуска». ## Модель по проходу Следует из правила 2: чем больше работы делает детерминированный инструмент, тем дешевле может быть модель; чем больше проход **порождает** критерий, тем дороже. Модель задана во frontmatter каждого агента, менять её здесь не нужно. | Модель | Проходы | Почему | |---|---|---| | `sonnet` | gate, code, ops | вход структурный, критерий записан заранее | | `opus` | specs, adversary, rubric, reimpl | суждение без опоры на инструмент | | `fable` | triage, architecture | ошибка распространяется дальше самой находки | **Самая дорогая модель — только двум проходам, и это калибровка, а не осторожность.** Замер: на первом же прогоне конвейера самые ценные находки дали `opus`-проходы — сверка спек дала 13 находок с оракулами, а проход про идиоматичность (впоследствии упразднённый) — три эксперимента против драйвера БД с воспроизведёнными числами. Разницы в пользу более дорогой модели на опиниативных проходах не обнаружилось — значит платить за неё там не за что. Двое, у кого она остаётся, отобраны по одному признаку: **их ошибка распространяется дальше собственной находки.** - `triage` — через него проходит всё, что оркестратор реализует **молча**: ложноположительная находка становится кодом, потерянный `critical` — дефектом. Ошибка триажа дороже ошибки любого отдельного прохода. - `architecture` — запускается редко (только `deep` и `design`), потолок в 3 находки делает его дешёвым по выходу, а находка на предложении стоит абзаца против переписывания на готовом коде. Дёшево × высокое плечо. `reimpl` намеренно **не** в этом списке, хотя он самый ценный из generative: его стоимость определяется объёмом вывода (он пишет реализацию целиком), так что дорогая модель множит самый большой счёт. Ценность же его — в **независимости** взгляда, а не в мощности модели. **Самая дешёвая модель не используется ни на одном проходе, и это не экономия наоборот.** Дешёвая модель на опиниативном проходе даёт правдоподобные находки, которые триаж обязан опровергать оракулом, — а это самая дорогая операция конвейера. Механизируемая же работа здесь вынесена **ниже** модели: гейт, покрытие диффа, карта проекта — это скрипты проекта, они стоят ноль токенов. Дешёвому проходу просто не осталось работы. Экономия достигается не понижением модели, а **непуском прохода**: `quick` — четыре прохода, `deep` — семь-восемь. Правило выбора профиля и есть главный рычаг стоимости. ## Профили | Профиль | Когда | Стадии | Проходов | |---|---|---|---| | `quick` | багфикс, локальная правка, доки | 0, 1, 5 | 4 | | `standard` | новая функциональность в существующем пакете | 0, 1, 2, 5 | 6 | | `deep` | новый пакет, изменение публичного контракта, миграция схемы, трогает инварианты проекта | 0, 1, 2, 3, 4, 5 | 7–8 | | `design` | **до кода**, на предложении | specs + rubric + architecture (см. ниже) | 3 | **Состав сверяется по этой таблице до коммита.** Реестр из трёх-восьми пунктов проверяется взглядом — и это единственная защита от промаха, который уже случился: пропуск прохода **не отличим от прохода без находок** (гейт зелёный, спеки сошлись, отчёт выглядит полным), а заметить его мог бы только триаж, который сам заполняется тем, что ему подали. Отчёт обязан перечислять запущенные проходы **поимённо и с исходом**; непущенный идёт строкой «не запускался» в границы покрытия, а не отсутствует. Цена молчащего пропуска измерена: семь находок и отдельная задача на их дозакрытие. Правило выбора профиля — **по факту изменения, не по ощущению важности**: - есть миграция схемы, новый пакет, изменение публичного контракта (API, протокол, формат на диске) или трогается правило, определяющее идентичность и слияние данных → `deep`; - иначе меняется поведение, видимое снаружи (эндпоинт, форма ответа, код ответа, формат лога) → `standard`; - иначе → `quick`. Что именно в этом проекте считается публичным контрактом и какие пути означают `deep`, проект может уточнить в `docs/review.md`, разделе настройки конвейера. Это **уточнение**, а не отмена: не записано — работает список выше. Профиль объявляется в отчёте. Понижение профиля — решение оркестратора, и оно попадает в границы покрытия строкой «профиль понижен до X, потому что …». ## Режим запуска: параллельно или последовательно Профиль отвечает «какие проходы», режим — «как их запускать». Стадии всегда идут по порядку номеров; выбор касается только проходов **внутри** стадии. | Режим | Как | Когда | |---|---|---| | **последовательно** (умолчание) | по одному, следующий стартует после отчёта предыдущего | всегда, пока не попросили иначе | | **параллельно** | названные проходы — одним сообщением | только по явной просьбе **и** с явно названным набором | **Умолчание — последовательно, и его не надо обосновывать.** Обосновывается отступление. **Параллельный режим включается при двух условиях сразу**, и второе так же обязательно, как первое: 1. **о нём попросили явно** — «гони параллельно», а не «сделай побыстрее»; 2. **названо, что именно гнать параллельно** — поимённый набор проходов («`specs` и `code` параллельно») или стадия целиком («стадию 1 параллельно»). Просьба без набора — **не основание**: гоним последовательно и одной строкой говорим, что набор не был назван. Это не придирка к формулировке. Параллелить можно ровно то, что не мешает друг другу, а знание об этом лежит у того, кто просит: он видит, занята ли машина, и ждёт ли он от прогона замеров. Домысливать набор за него — значит принять решение, которое он оставил себе. Почему умолчание именно такое: - **Замеры.** Проходы `adversary` и `ops` доказывают находки числами: время удержания блокировки против её таймаута, пик кучи против размера тела, темп роста файлов журнала, длительность транзакции. Два меряющих прохода на одной машине соревнуются за диск, CPU и за саму СУБД и выдают числа, которые не воспроизведутся. Это не гипотеза: правило выведено из находок, целиком державшихся на таких замерах, — у каждого проекта они свои и лежат в журнале `docs/review.md`. Число, снятое под конкурентную нагрузку от соседнего прохода, — это находка с испорченным оракулом, а её опровержение стоит дороже всего выигрыша от параллельности. - **Машина одна.** Рядом идёт задача, поднят сервис, гоняется гейт или дорогая проверка проекта. - **Ранний выход** возможен только при последовательном прогоне (см. ниже). - **Разбор самого конвейера.** Когда выясняется, почему проход чего-то не нашёл, порядок и изоляция важнее скорости. Если параллельный режим всё же включён, в границы покрытия идёт строка: какие проходы шли разом и что замеры, снятые в этом прогоне, как оракул слабее. **Чего режим не меняет — и это не подлежит обсуждению.** Проход **не видит** находок других проходов ни в каком режиме. «Последовательно» значит «по очереди», а не «следующий читает предыдущего». Вся ценность конвейера держится на декорреляции: под всеми ролями одна модель с одними априорными, и стоит показать ей чужой вывод — она согласится. Согласие нескольких проходов и так не повышает `confidence` (см. «Честный предел»); согласие **наведённое** ещё и маскируется под независимое подтверждение. Единственный, кто видит всё, — триаж, и это его работа. **Ранний выход** (последовательный режим делает его возможным — это его побочная выгода, а не повод его выбирать). Допустимо остановить прогон, не докатив остаток, ровно в одном случае: находка требует **переделки формы** изменения, и остальные проходы будут смотреть на код, которого через час не станет. Тогда: - прогон останавливается, находка чинится, конвейер запускается **заново с нулевой стадии** — а не «доезжает» остатком по старому коду; - незапущенные проходы идут в границы покрытия строкой «не запускался: прогон остановлен на <проход> из-за <находка>», поимённо; - триаж запускается только на полном прогоне. Отчёт триажа по половине проходов выглядит полным, потому что агрегирует всё, что ему подали, — это тот же молчащий пропуск, что и в разделе «Профили». Ранний выход по находке, которая чинится в пределах существующей формы (`Действие: инлайн`), **не делается**: дешевле дособрать все находки и починить пачкой, чем гонять конвейер дважды. Режим объявляется в отчёте наравне с профилем, и если он **параллельный** — с причиной и составом. Последовательный объявляется одним словом. ## Стадия 0 — Gate (обязательна во всех профилях) Агент `review-gate`. Запускает команду гейта из семантики гейта в `CLAUDE.md` и интерпретирует вывод. **Пока гейт красный — опиниативные проходы не запускаются.** Оркестратор чинит и перезапускает гейт. Исключение одно: отказ, унаследованный от базовой ветки (гейт проверяет это прогоном на базе) — тогда он фиксируется находкой и не блокирует. Гейт возвращает не только «зелено/красно», но и находки класса **отсутствующая верификация**: изменённые строки без покрытия, конкурентность без теста с параллельным доступом, флаки-тест (не ниже `major`), недоступный инструмент. Шаги выбираются по изменённым файлам: правка документации не гоняет тесты, линтеры и детектор гонок. Пропуск при этом не молчит — он виден в сводке с причиной и уезжает в границы покрытия, как и любой другой `SKIP`. Шаги, которые красят гейт безусловно, перечислены в `CLAUDE.md` с причиной. Проходу запрещено списывать такой отказ в мелочь. ## Стадия 1 — Conformance (обязательна во всех профилях) Два applicative-прохода: оба применяют **записанный** критерий, оба дешёвые. Замеров они не делают и потому безобиднее прочих, если параллельный режим попросят с их именами; сами по себе идут по очереди, как и все. - `review-specs` — критерий взят из **дельта-спек предлагаемого изменения**, а не из proposal, сообщения коммита или описания задачи. Сверка двунаправленная; направление `code → spec` важнее. - `review-code` — критерий взят из конвенций проекта, каталог `docs/conventions/`. Берётся только та их часть, которая **не выражается правилом**: механизируемое уже проверила стадия 0. Что именно механизировано, перечисляет `conventions/README.md` — повторять это проходом вредно. Recall обоих равен длине их источника — это и есть предел applicative-проходов, ради которого существует стадия 2. ## Стадия 2 — Adversarial и operational (`standard`, `deep`) Два прохода: - `review-adversary` — находка есть **построенный путь**, а не свойство; - `review-ops` — постмортем от симптома у владельца сервиса к строке кода. **Эту пару параллелить не стоит даже по просьбе — переспроси.** Оба доказывают находки замером, и оба меряют одно и то же железо. Запущенные разом, они портят числа друг другу, а испорченный оракул хуже отсутствующего: находка выглядит доказанной. Если их всё же назвали в параллельном наборе — выполняй, но скажи в границах покрытия, что числа этого прогона сняты под соседней нагрузкой. **Эта стадия зарабатывает больше всех остальных вместе, и потому стоит в `standard`, а не только в `deep`.** Измерено на пяти задачах подряд: враждебный проход дал пять из семи выживших находок дозапуска (включая обе верхние); эксплуатационный — единственный, кто нашёл, что откат бинаря поверх новой схемы стартует молча. Оба несут внешний оракул по построению: один обязан путь **прогнать**, второй смотрит ось времени и эксплуатации, которую не смотрит никто другой. Материал берётся из документов: `docs/security.md` — враждебному, `docs/architecture.md`, `docs/research/` и `docs/database.md` — эксплуатационному. Что с чем сшивать и почему — [project-facts.md](references/project-facts.md), раздел «Сшивать обязаны проходы». Без этих документов стадия вырождается в общие места. ## Стадия 3 — Independent reimplementation (`deep`, по триггеру) - `review-reimpl` — пишет свою реализацию, не открывая существующую, затем диффит по решениям. **Запускается по триггеру, а не всегда:** изменение вводит новое правило идентичности, слияния или разбора (проектная формулировка триггера — в `docs/review.md`, если записана). Это самый дорогой проход конвейера (его счёт определяется объёмом вывода — он пишет реализацию целиком), а вне этого триггера независимый взгляд в значительной мере уже дал профиль `design`: код писался под его находки. Триггер выбран по факту: единственный раз, когда триаж назвал отсутствие `reimpl` дырой покрытия, — это была задача с новым правилом слияния сущностей. ## Стадия 4 — Global (`deep`, `design`) Агент `review-architecture`. Получает **вход шире диффа**: дерево пакетов с назначением, граф внутренних зависимостей, инвентарь существующих концепций. Команду, которая это готовит, даёт раздел команд `CLAUDE.md`; нет команды — проход собирает карту сам и говорит об этом в границах покрытия. Главный вопрос — концептуальная целостность и **второй способ** делать то, что уже делается. Он же и оправдывает проход: на задаче про пересборку архитектурный проход нашёл, что новый код был **вторым проигрывателем журнала** со своим порядком. Второй обязательный вопрос — **что опытный человек отсюда удалил бы**: слой с единственной реализацией, интерфейс ради мока, незапрошенная конфигурируемость, подстраховка поверх подстраховки. Потолок — 3 находки плюс секция «дешевле переделать до мерджа». ## Стадия 5 — Triage (обязательна) Агент `review-triage`. Единственный, кто агрегирует. Получает сырые выводы всех проходов, `git diff`, профиль, режим и **список запущенных проходов**; возвращает финальный отчёт. Без триажа проходы дают порядка сорока замечаний при единицах существенных. Потребитель здесь — оркестратор, который **молча реализует** всё, что прочитал: цена нетриажированного отчёта — не потерянное время человека, а разросшийся от вкусовщины код. Порядок: дедупликация по причине → оракул для всего `critical`/`major` → понижение неподтверждённого до гипотезы → отсев вкусовщины → ранжирование по ущербу × вероятности → потолок 7 пунктов в основном списке. ## Профиль `design` — до кода Запускается на шаге ревью спек (шаг 4 скилла `av-dev-pipeline:task-pipeline`), когда change уже имеет `proposal.md` и дельта-спеки, но кода ещё нет. Состав: 1. `review-specs` в режиме «дизайн ДО кода»; 2. `review-rubric`, фаза 1 без фазы 2: рубрика на задуманный узел становится приёмочными критериями и уезжает в `tasks.md`; 3. `review-architecture` на предложении: вводит ли change новое понятие, можно ли выразить существующими — **включая конструкции стандартной библиотеки**, — не появляется ли второй способ. Вопрос «не изобретаем ли то, что уже есть в библиотеке» живёт здесь; 4. вопрос автору дизайна: **«предложи три формы решения и назови компромисс каждой»** — если ответ показывает, что рассматривалась одна, это находка. Смысл профиля: архитектурная находка на готовом коде стоит переписывания и поэтому игнорируется; та же находка на предложении стоит абзаца обсуждения. `rubric` живёт **только** в этом профиле. Судить код по критерию, под который он писался, — корреляция по построению; те же 8–14 свойств уже лежат приёмочными критериями в `tasks.md`. ## Контракт находок Единый для всех проходов — [references/finding-contract.md](references/finding-contract.md). Коротко: заголовок через **последствие**, обязательные поля `Файл`, `Severity`, `Confidence`, `Оракул`, `Последствие`, `Предложение`, `Найдено проходом`. `critical` без оракула или построенного пути не существует. Находка без поля «Последствие» не выводится вовсе. Каждый проход завершает вывод блоком `## Coverage of this pass`. ## Что происходит с находками дальше - Оркестратор чинит помеченное `Действие: инлайн` и **не логирует мелочь**. - `Действие: развилка` — вопросом с вариантами и ценой каждого туда, где проект держит вопросы (это знает вызвавший пайплайн, а не конвейер). Оркестратор не останавливается: он урезает изменение до остатка и доводит его. - Находка не для этого мерджа, но реальная (отложенный `major`, развилка, решённая «потом»), — не теряется, но **и не заводится здесь**. Конвейер отдаёт её **списком урожая** в отчёте: формулировка, оракул, провенанс (какой проход, какой change). Заведение задач принадлежит тому, кто ведёт задачи проекта, — у него свой формат, своя нарезка и свои правила дублей. Мелочь класса `nit` идёт в урожай одной пачкой, а не записью на находку. - `Promote candidates` — по процедуре [references/promote.md](references/promote.md): находка → конвенция → правило линтера → **удаление формулировки из конвенций**. Третий шаг обязателен. - Дефект, проскочивший ревью и всплывший позже, идёт в журнал проекта ([references/review-journal.md](references/review-journal.md)) — сразу, не ретроспективно: теряется именно причина непоймания. - **Отчёт триажа сохраняется вместе с изменением** (например, в `openspec/changes//review/`). Он единственное, по чему потом видно, что было найдено и что из этого не заведено: нулевой урожай при непустом отчёте виден сразу. ## Честный предел Модель воспроизводит медиану публичного кода, смещённую к популярному и туториальному: отсюда тяга к интерфейсам ради интерфейсов, лишним мокам и конфигурируемости, которую никто не просил. **«Идиоматично» и «распространено» — разные вещи**; проходы обязаны различать их и опираться на поимённое положение гайда, а не на ощущение частотности. Согласие нескольких проходов — **не подтверждение**: это один источник, высказавшийся несколько раз. Совпадение повышает приоритет, но не `confidence`. Что недоступно **этому** проекту принципиально — перечисляет «Недоступно проверке» в `docs/review.md`, и оба его подраздела целиком уезжают в границы покрытия. Независимо от проекта недоступно: - поведение внешних систем в их будущих версиях; - реальный профиль нагрузки и то, что на самом деле лежит в данных; - завязка внешних потребителей на текущую форму ответа; - суждение «этой функциональности не должно существовать». Отдельно и честно: **поимённая сверка с положениями стайлгайдов языка не задаётся ни одним проходом.** Проход про идиоматичность упразднён, его способные части переселены (эксперимент против поведения библиотеки и драйвера — в `ops`, вопрос 8; «не изобретаем ли то, что уже есть в библиотеке» — в `architecture`, вопрос 1), но различение «идиоматично против распространено» теперь не спрашивает никто. Класс обратимый — портит форму кода, не данные, — и его надо признавать в границах покрытия, а не считать проверенным. Это и есть причина, по которой конвейер готовит ревью, а не заменяет его. ## Ссылки - [references/project-facts.md](references/project-facts.md) — что нужно проходу и где это лежит в документах проекта; таблица поразрядной деградации. - Skill `av-dev-pm:canon` — приведение проекта к канону документов. - [references/finding-contract.md](references/finding-contract.md) — контракт находок. - [references/promote.md](references/promote.md) — промоут находка → конвенция → правило → удаление. - [references/calibration.md](references/calibration.md) — калибровка инъекцией, вердикты keep/retune/drop. - [references/review-journal.md](references/review-journal.md) — журнал проскочивших дефектов.