--- name: review-pipeline description: "Конвейер ревью изменения, устроенный по темам: каждый документ проекта — тема ревью, а проход лишь закрывает тему на заданной глубине. Ядро тем — requirements, autotests, conventions, architecture, security, operations; список открытый, свои темы проект заводит документом. Прогон начинает разметчик: находит документы, выводит темы, выбирает ступень с обоснованием и раздаёт темы проходам. Три ступени: quick и standard закрывают все темы (сверкой и разбором), wide добавляет доказательство — враждебные постановки, эксплуатационный постмортем, архитектурный проход на широком входе. Триаж обязателен всегда. Порядок прогона — граф зависимостей: гейт открывает опиниативные проходы, проходы с пометкой «держит машину» идут цепочкой, триаж — единственный сток. Проектная специфика приходит из документов канона av-dev-pm. Вызывается из task-pipeline (чекпоинты ревью), из task-batch (финальная сверка) и отдельно — профилем design на предложении ДО кода." --- # Конвейер ревью Готовит ревью — **не заменяет его**. Потребитель отчёта — оркестратор, который чинит код; человек читает только сводку, развилки и границы покрытия. ## Четыре правила, из которых всё следует Если ситуация не покрыта инструкцией — решай по ним. 0. **Тема первична, проход вторичен.** Ревью проверяет **темы** — набор направлений, который проект объявляет своими документами. Проход это только способ закрыть тему на заданной глубине, и проходы меняются: уезжают в верхнюю ступень, сливаются, упраздняются. Если состав прогона считать списком проходов, то уехавший проход уносит тему с собой **беззвучно** — отчёт честно скажет «`ops` не запускался» и не скажет «эксплуатацию не смотрел никто», а нужно второе. Поэтому прогон описывается таблицей «тема → глубина → кто закрывает», и таблица эта есть в каждом отчёте. 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`. ## Темы — и почему их список открытый **Каждый документ проекта — тема ревью.** Форма дома значения не имеет: `docs/security.md` и `docs/security/` — одна тема `security`, проект выбирает форму по объёму написанного. Завёл документ — завёл тему; запретить нельзя, разрешения не надо. Из этого следует то, ради чего правило и заведено: **`docs/` перестаёт быть просто документацией и становится конфигурацией конвейера**. Проект настраивает ревью тем, что пишет о себе, а не отдельным файлом настроек, который разошёлся бы с документами. Ядро — шесть тем, они есть у любого проекта, приведённого к канону: | Тема | Дом | Вопрос темы | |---|---|---| | `requirements` | `openspec/specs/`, дельты change | делает ли код заказанное, и только его | | `autotests` | `CLAUDE.md`: семантика гейта, команды | проверено ли машиной и хватает ли проверок | | `conventions` | `docs/conventions.*` | написано ли так, как здесь пишут | | `architecture` | `docs/architecture.*`, `passport.*`, `adr/` | цело ли устройство: понятия, границы, решения | | `security` | `docs/security.*` | что сделает недоверенный вход | | `operations` | `docs/architecture.*`, `database.*`, `research/` | что будет через неделю на проде | Не темы — их ровно две: `docs/tasks/` (каталог задач, его ведёт `av-dev-pm:tasks`) и `docs/review.*` (настройка самого конвейера и журнал дефектов — слой **над** темами). Обе называются в плане явно, а не пропускаются молча. **Проектная тема закрывается `basics`**, на любой ступени. Именных проходов конечное число, а тем — сколько заведёт проект; приёмник обязателен, иначе открытость списка была бы обещанием без механизма. **Тема без дома — законное состояние и отдельная строка.** «Тема `operations` заявлена, `docs/database.md` нет» читается иначе, чем «не смотрели». Деградация поразрядная: нет дома — падает глубина этой темы, и только её. Что именно проход читает по каждой теме — [references/project-facts.md](references/project-facts.md). Отдельного файла-брифа при этом нет: пути известны, посредник не нужен, а второй дом для тех же фактов разошёлся бы и выглядел актуальным. **Документов канона нет вовсе** — проект не приведён к канону. Скажи это строкой и предложи скилл `av-dev-pm:canon`: одна операция на проект против деградации на каждой задаче. Прогон при этом не останавливается. ## Что получает каждый проход Задание собирается **по плану разметчика** (стадия 0) и состоит из шести вещей: - **его темы** — какие темы он закрывает на этом прогоне, у каждой **дом** (путь и раздел) и **глубина**. Дом передаётся адресом, а не пересказом: проход, получивший проинтерпретированный периметр, не заметит, что интерпретация неверна; - **вопросы по его темам** из `docs/review.*`, если они там есть, — **дословно**. Вопрос привязан к теме, а не к имени прохода, и потому переживает переезд прохода между ступенями; - **контракт находок** — путь к [references/finding-contract.md](references/finding-contract.md) (в установленном плагине — `${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/`); - **изменение** — идентификатор change и путь к его дельта-спекам; - **база диффа**; - **профиль и режим** прогона — чтобы проход знал, что писать в границы покрытия. Чего проход **не** получает ни в каком режиме — выводов других проходов. См. «Порядок прогона». ## Модель по проходу Модель выбирается **по цене ошибки прохода, а не по его роду**. Признак рабочий и проверяемый: находка со ссылкой на записанный источник — строку спеки, цель в манифесте, значение в конфиге — опровергается открытием файла, и дешёвая модель ошибается здесь проверяемо; находка-суждение опровергается рассуждением, а рассуждение стоит триажа или человека. Второй род ошибки — **пропуск**: он не стоит ничего сегодня и не виден вовсе, и проход, у которого дороже пропустить, держится наверху, даже будучи applicative. Модель задана во frontmatter каждого агента, менять её здесь не нужно. | Модель | Цвет | Проходы | Почему | |---|---|---|---| | `sonnet` | green | scope, gate, ops | вывод перечислим и сверяется механически | | `opus` | yellow | specs, code, basics, adversary, rubric, architecture, triage | дорога ошибка — ложная либо пропущенная | **Цвет charter'а кодирует модель, а не роль прохода.** Это единственное назначение цвета: список агентов читается взглядом, и по нему сразу видно, чем платит прогон. Роль прохода из имени и так понятна, а цвет, розданный по ролям, не отвечает ни на один вопрос, который задают во время прогона. Раскладка живёт здесь и **проверяется механически** — цвет ставится один раз при заведении charter'а, а модель потом двигает калибровка, и разъезжаются они молча. **Моделей две, и верхняя из них — `opus`; выше неё конвейер не платит.** Замер: на первом же прогоне самые ценные находки дали `opus`-проходы — сверка спек дала 13 находок с оракулами, а проход про идиоматичность (впоследствии упразднённый) — три эксперимента против драйвера БД с воспроизведёнными числами. Разницы в пользу модели **дороже** `opus` не обнаружилось ни на одном проходе, а прогон на ней стоил заметно дольше и дороже — значит платить за неё не за что. Четверо держатся наверху не за суждение, а по отдельным причинам, и их стоит знать поимённо: - `triage` — через него проходит всё, что оркестратор реализует **молча**: ложноположительная находка становится кодом, потерянный `critical` — дефектом. Ошибка триажа дороже ошибки любого отдельного прохода. - `specs` — по устройству applicative, но направление `code → spec` требует заметить **отсутствие**: тихий фолбэк, самодеятельный дефолт, проглоченную ошибку. Здесь дорог пропуск, а не ложная находка. - `code` — единственный, кто читает код **как код**. Его пропуск это дефект в проде, и он тоже не оставляет следа ни в отчёте, ни в границах покрытия. По той же причине, что `specs`, и это дороже всего в конвейере: проход идёт на каждой задаче. - `architecture` — запускается только в верхней ступени, на 5–10% задач, потолок в 3 находки делает его дешёвым по выходу, а находка на предложении стоит абзаца против переписывания на готовом коде. Дёшево × высокое плечо. **`scope` внизу, и это не противоречие, хотя его ошибка расходится дальше всех.** Его работа распадается надвое: поиск документов и раздача тем **перечислимы** — план сверяется с `ls docs/` за секунду, пропущенный документ виден без рассуждения; выбор ступени — суждение, но у него есть три независимых корректора: отрицательный тест `quick`, правило «спорный случай вниз» и сигнал `basics` о заниженной ступени. Дешёвая модель безопасна ровно потому, что её вывод устроен как список, а не как мнение. **Самая дешёвая модель не используется ни на одном проходе, и это не экономия наоборот.** Дешёвая модель на опиниативном проходе даёт правдоподобные находки, которые триаж обязан опровергать оракулом, — а это самая дорогая операция конвейера. Механизируемая же работа здесь вынесена **ниже** модели: гейт, покрытие диффа, карта проекта — это скрипты проекта, они стоят ноль токенов. Дешёвому проходу просто не осталось работы. Экономия достигается **не понижением модели, а глубиной и непуском**: `quick` и `standard` закрывают все темы, но чтением и рассуждением, а `wide` добавляет доказательство — запуск, замер, построенный путь. Именно доказательство и стоит часов: машина, цепочка меряющих проходов, ожидание. Второй рычаг — **вход и потолок прохода**. `basics` идёт на верхней модели, но с узким входом (дифф и его окрестности, без карты проекта) и жёстким потолком: 2 находки на сверке, 4 на разборе. Дешевле он не от модели, а от того, чего **не** делает. ## Профили **Ступень не меняет список тем — она меняет их глубину.** Все темы закрыты во всех профилях; разница в том, читают ли их, рассуждают над ними или доказывают запуском. | Тема | `quick` | `standard` | `wide` | |---|---|---|---| | `requirements` | `specs` | `specs` | `specs` | | `autotests` | `gate` | `gate` | `gate` | | `conventions` | `code` | `code` | `code` | | `architecture` | `basics`, сверка | `basics`, разбор | `architecture`, доказательство | | `security` | `basics`, сверка | `basics`, разбор | `adversary`, доказательство | | `operations` | `basics`, сверка | `basics`, разбор | `ops`, доказательство | | тема проекта | `basics`, сверка | `basics`, разбор | `basics`, разбор | Отсюда состав: | Профиль | Когда | Стадии | Проходов | Доля задач | |---|---|---|---|---| | `quick` | мелкое: багфикс, мелкая фича, локальная правка, доки | 0, 1, 2, 3, 5 | 6 | много | | `standard` | **рабочее умолчание**: всё, что не мелкое и не крупное | 0, 1, 2, 3, 5 | 6 | большинство | | `wide` | крупное или незнакомое: большой рефакторинг, функциональность, форму которой ещё предстоит нащупать | 0, 1, 2, 4, 5 | 8 | **5–10%** | | `design` | **до кода**, на предложении | specs, плюс rubric и architecture по условию `wide` | 1–3 | — | **`quick` и `standard` совпадают составом и различаются глубиной** — это единственное место конвейера, где профиль не выводится из одного лишь списка проходов. Поэтому глубина объявляется в отчёте наравне с профилем, а план разметчика называет её по каждой теме. Проверять надо два факта вместо одного, и оба напечатаны. **Три глубины, и они не про старательность, а про способ доказательства.** **Сверка** — открыть дом темы, открыть дифф, сравнить. **Разбор** — построить сценарий рассуждением, ничего не запуская. **Доказательство** — прогнать, померить, построить путь. Только третья требует машины, и только она стоит часов. **`wide` назван по тому, что он добавляет: вход шире диффа.** Он единственный, где живут тяжёлые проходы, и единственный, где что-то **запускается**. `basics` в нём берёт только проектные темы; своих тем у проекта нет — он не запускается вовсе, и план говорит об этом строкой. **Доля 5–10% — не пожелание, а проверка правила.** Если `wide` уходит каждая третья задача, ступень выбирают по ощущению важности. Обратный перекос виден по журналу проскочивших дефектов: класс, который ловят только меряющие проходы, начинает всплывать после мерджа. **Состав сверяется до коммита — по плану разметчика, а не по этой таблице.** План и есть реестр: тема, дом, глубина, кто закрывает. Это единственная защита от промаха, который уже случился: пропуск **не отличим от прохода без находок** (гейт зелёный, спеки сошлись, отчёт выглядит полным), а заметить его мог бы только триаж, который сам заполняется тем, что ему подали. Непущенное идёт строкой «не запускался» с причиной, а не отсутствует. Цена молчащего пропуска измерена: семь находок и отдельная задача на их дозакрытие. Правило выбора — **два вопроса по факту изменения, не по ощущению важности**. Дом правила здесь, а применяет его `review-scope` на стадии 0 — не автор изменения. Отвечать по порядку, первый подошедший ответ и есть профиль: 1. **Изменение крупное или незнакомое?** → `wide`. Крупное — трогает несколько узлов или слоёв разом, переносит ответственность между ними, перекладывает существующий код в новую форму (большой рефакторинг). Незнакомое — функциональность, которой в проекте ещё не было, и **форму решения предстоит нащупать по ходу**, а не выбрать до начала. Признак незнакомого простой: перед работой нельзя назвать, какие узлы будут тронуты. 2. **Изменение мелкое?** → `quick`. Мелкое — помещается в один узел, форма решения очевидна до начала работы, а откат сводится к обратной правке. Сюда идут мелкий багфикс, мелкая фича, правка текста и документации. 3. **Всё остальное** → `standard`. Это рабочее умолчание, и оно должно набирать большинство задач. **Два признака смотрят на разное, и в этом весь смысл двух вопросов.** Первый — про **объём и неизвестность**: сколько мест трогается и знаем ли мы форму решения заранее. Второй — про **обратимость**: во что обойдётся ошибка, если она уедет в мердж. Раньше ступень выбиралась только по классу изменения («вводит ли новое понятие»), и объём в правило не входил вовсе; теперь входит, потому что цена разбирательства растёт именно с ним. **Отрицательный тест `quick`, и он важнее положительного:** изменение, которое после мерджа **не откатывается обратной правкой**, — не `quick`, каким бы маленьким ни был дифф. Сюда попадают миграция схемы и данных, формат на диске, публичный контракт, имя, которое разойдётся по кодовой базе. Три строки миграции — это `standard`, а не `quick`: размер диффа и цена ошибки здесь расходятся. Что здесь считается крупным и что — незнакомым, проект может уточнить в `docs/review.md`, разделе настройки конвейера: поимённо, узлами или capability. Это **уточнение**, а не отмена: не записано — работает список выше. ### Спорный случай решается вниз, и у этого есть цена Правило асимметрично, потому что асимметрична цена ошибки. - **Спорно между `standard` и `wide` → бери `standard`.** Ошибка в эту сторону стоит находки, которая всплывёт на следующей задаче или в журнале дефектов. Ошибка в обратную стоит трёх тяжёлых проходов, двое из которых держат машину и идут цепочкой, — и платится она **на каждой** задаче, выбранной неверно. - **Спорно между `quick` и `standard` → бери `standard`.** Здесь состав тот же, и разница только в глубине трёх тем: сверка против разбора. Дёшево, и потому сомнение решается в пользу разбора. **Выбор сделан в пользу пропускной способности, и это записано, а не подразумевается.** Конвейер настроен на поток задач, а не на максимум находок с каждой: поправить в следующей задаче дешевле, чем держать одну два часа. Отсюда три обязанности, без которых сделка превращается в незаметную потерю качества: - **границы покрытия называют темы и их глубину**, а не только запущенные проходы — иначе `quick` выглядит так же, как `wide` без находок; - **журнал дефектов в `docs/review.md` перестаёт быть хорошей практикой и становится единственной обратной связью**: проскочивший дефект — единственный сигнал, что ступень выбрана слишком низко; - **возврат в код — повод пересмотреть ступень.** Задача, которая приходит в тот же узел третий раз, уже не мелкая, чем бы ни выглядел её дифф. ### Профиль — максимум по поверхности Условия читаются сверху вниз, и **первое подошедшее отвечает за весь дифф**. Профиль изменения это максимум по его поверхности, а не средневзвешенное: одна строка в перечне границ задачи поднимает ступень всему остальному, включая ту часть, которая сама по себе была бы `quick`. Обратное тоже верно и тоже не бесплатно: у каждой задачи есть **несокращаемый костяк из шести проходов** (разметка, гейт, спеки, код, темы, триаж). Разрезать задачу, обе половины которой остаются в одном профиле, — значит заплатить костяк дважды за ту же проверку. Резать стоит там, где разрез **снимает доказательство с большей части диффа**. Шов и правило нарезки живут у того, кто ведёт задачи, — скилл `av-dev-pm:tasks`, его `references/split.md`. Пути туда конвейер не выносит: за пределы своего плагина он ходит вызовом скилла, а не файлом. **Профиль и глубина объявляются в отчёте, и оба с обоснованием.** Ступень выбирает `review-scope`; он вправе и поднять, и понизить её — но не молча: строка «ступень X, потому что …» обязательна на каждом прогоне, а не только когда ступень отличается от ожидаемой. ## Порядок прогона — граф, а не очередь Профиль отвечает «какие темы и на какой глубине», порядок — «что кого ждёт». Стадии остаются единицей **состава**, но порядок задают **не их номера**: между стадиями 2–4 настоящих зависимостей нет — ни один проход не читает вывод другого, — и очередь между ними была бы платой ни за что. Рёбер два вида, и они разной природы. Путать их нельзя: первое про **осмысленность** (без плана задание не определено, на красном гейте опиниативный проход не о чем), второе про **железо**. | Ребро | Смысл | Между кем | |---|---|---| | **зависимость** | B не стартует, пока A не закончил, потому что без A задание B не определено | разметка → все; гейт → все опиниативные; все проходы → триаж | | **конфликт за ресурс** | A и B не держат машину одновременно; кто из них первый — неважно, направления у ребра нет | проходы, помеченные «держит машину» | ```mermaid flowchart TD scope["scope — разметка
(стадия 0, темы и ступень)"] gate["gate
(стадия 1, держит машину)"] specs["specs"] code["code"] basics["basics
(quick, standard: темы;
wide: только свои темы проекта)"] adversary["adversary
(wide, держит машину)"] ops["ops
(wide, держит машину)"] architecture["architecture
(wide)"] triage["triage — единственный сток"] scope -->|план| gate gate -->|зелёный| specs gate -->|зелёный| code gate -->|"зелёный, темы по плану"| basics gate -->|"зелёный, wide"| adversary gate -->|"зелёный, wide"| ops gate -->|"зелёный, wide"| architecture adversary -. один ресурс — машина .- ops specs --> triage code --> triage basics --> triage adversary --> triage ops --> triage architecture --> triage ``` Читается граф так: **всё, у чего входящие рёбра закрыты, уходит одним сообщением**. Разметка идёт первой и одна — до неё неизвестно ни что проверять, ни на какой ступени. В `quick` и `standard` после зелёного гейта уходят разом `specs`, `code` и `basics`, и сразу триаж. В `wide` вместо тем `basics` идут три тяжёлых: `architecture` и первый из меряющей пары — сразу, второй меряющий — следом за первым, и он же определяет, когда стартует триаж. **Схема здесь старше прозы.** Она не иллюстрация к тексту, а сам алгоритм планировщика; проза ниже объясняет рёбра и называет их цену. Разошлись — прав граф, а расхождение чинится правкой текста. **Ребро значит «A закончил раньше, чем B стартовал», и ничего больше.** В обычном графе задач ребро тянет за собой данные — здесь нет, и это не деталь реализации. Проход **не видит** находок других проходов, в каком бы порядке их ни запустили. Вся ценность конвейера держится на разведённости: под всеми ролями одна модель с одними априорными, и стоит показать ей чужой вывод — она согласится. Согласие нескольких проходов и так не повышает `confidence` (см. «Честный предел»); согласие **наведённое** ещё и маскируется под независимое подтверждение. Единственный, кто получает чужие выводы, — триаж, и это его работа. ### Кто держит машину Ресурс один и неделимый: **машина** — тесты, поднятый сервис, СУБД, порты, диск. Проходы, заявившие его, сериализуются между собой в любом профиле и на любой стадии; порядок внутри цепочки произволен. | Проход | Держит машину | Почему | |---|---|---| | `gate` | да | запускает инструменты проекта — но он источник графа и один по построению | | `adversary` | да | находка есть **построенный путь**: он пишет падающий тест и гоняет его | | `ops` | да | доказывает числами: время удержания блокировки, пик кучи, темп роста журнала | | `triage` | да | проверяет оракул `critical`/`major` запуском — но он сток и тоже один | | `scope`, `specs`, `code`, `basics`, `architecture`, `rubric` | нет | читают и рассуждают, ничего не исполняют | **Правило про ресурс, а не про имена.** Раньше здесь стояло именованное исключение «`adversary` и `ops`»; оно рассыпается, как только проход начнёт мерить или в проекте появится свой. Два прохода на одной машине соревнуются за диск, CPU и за саму СУБД и выдают числа, которые не воспроизведутся, — а число, снятое под конкурентную нагрузку, это находка с испорченным оракулом. Её опровержение стоит дороже всего выигрыша от параллельности, и она хуже отсутствующей: выглядит доказанной. Правило выведено из находок, целиком державшихся на таких замерах; у каждого проекта они свои и лежат в журнале `docs/review.md`. Проект вправе пометить «держит машину» и другой проход — в `docs/review.md`, разделе настройки конвейера. Снимать пометку с перечисленных нельзя. ### Находка «переделать форму» — прогон повторяется целиком **Барьера стоимости в конвейере нет, и раннего выхода тоже.** Барьер существовал ради независимой реализации — единственного прохода, чей счёт определялся объёмом вывода, — и ушёл вместе с ней. Граф во всех профилях плоский, от гейта до триажа: защищать за барьером нечего, `architecture` дёшев по выходу (потолок 3 находки), а сериализация не бесплатна — она разводит по очереди то, что могло идти разом. Находка «**форму изменения** надо переделывать» ловится триажем, как и любая другая; дальше правило одно. Находка чинится, и конвейер запускается **заново с нулевой стадии**, а не «доезжает» остатком по коду, которого через час не станет. Если прогон всё же остановлен на полпути, незапущенные проходы идут в границы покрытия строкой «не запускался: прогон остановлен на <проход> из-за <находка>», поимённо, а **триаж на половине прогона не запускается**: его отчёт выглядит полным, потому что агрегирует всё, что ему подали, — это тот же молчащий пропуск, что и в разделе «Профили». Находка, которая чинится в пределах существующей формы (`Действие: инлайн`), прогон не останавливает: дешевле дособрать все находки и починить пачкой, чем гонять конвейер дважды. ### Линеаризация — когда графа мало Граф можно вытянуть в одну цепочку. Это отступление, и оно называется в отчёте: 1. **сказал оператор** — «гони линейно». Набора называть не надо: линейный прогон ничего не портит, он только дольше, и домысливать тут нечего; 2. **машина занята, и знает об этом вызывающий.** Рядом идёт другая задача, поднят сервис, гоняется дорогая проверка проекта. Сам конвейер занятости машины не видит — её обязан назвать тот, кто запускает; так и делает `av-dev-pipeline:task-batch`, когда ведёт задачи параллельно; 3. **разбор самого конвейера** — когда выясняется, почему проход чего-то не нашёл, порядок и изоляция важнее скорости. Обратное отступление — **слить цепочку ресурса** (пустить меряющие проходы разом) — бывает только по прямому слову оператора, и тогда в границы покрытия идёт строка: какие проходы шли одновременно и что замеры этого прогона как оракул слабее. Режим объявляется в отчёте наравне с профилем: **`по графу`** — одним словом, **`линейно`** — с причиной (какой именно из трёх). ## Стадия 0 — Разметка (обязательна во всех профилях) Агент `review-scope`. Идёт **первым, до гейта**, и один: до его плана неизвестно ни что проверять, ни на какой ступени. Возвращает **план прогона**: список тем с домами и глубинами, ступень с обоснованием, перечень документов, не ставших темами, и строку про найденные директивы (`CLAUDE.md`, `AGENTS.md`). План уезжает в отчёт целиком и служит границами покрытия. **Он не судит по существу** — ни одной находки об изменении. Его ошибка это пропущенная тема или не та ступень, и обе видны: план сверяется с `ls docs/` за секунду, а заниженную ступень ловит `basics` своим сигналом. **Ступень выбирает он, а не автор изменения.** Раньше профиль называл тот же оркестратор, который писал код: он же решал, насколько глубоко его проверять, — и разведённости с автором в этой точке не было вовсе. Вызывающий пайплайн профиль больше не передаёт; он передаёт change, базу диффа и режим. Право у разметчика симметричное: **поднять и понизить ступень он может одинаково**, но обоснование обязательно в обоих случаях и всегда — строкой, какой признак сработал и по какому факту. ## Стадия 1 — Gate (обязательна во всех профилях) Агент `review-gate`. Закрывает тему `autotests`. Запускает команду гейта из семантики гейта в `CLAUDE.md` и интерпретирует вывод. **Пока гейт красный — опиниативные проходы не запускаются.** Оркестратор чинит и перезапускает гейт. Исключение одно: отказ, унаследованный от базовой ветки (гейт проверяет это прогоном на базе) — тогда он фиксируется находкой и не блокирует. Гейт возвращает не только «зелено/красно», но и находки класса **отсутствующая верификация**: изменённые строки без покрытия, конкурентность без теста с параллельным доступом, флаки-тест (не ниже `major`), недоступный инструмент. Шаги выбираются по изменённым файлам: правка документации не гоняет тесты, линтеры и детектор гонок. Пропуск при этом не молчит — он виден в сводке с причиной и уезжает в границы покрытия, как и любой другой `SKIP`. Шаги, которые красят гейт безусловно, перечислены в `CLAUDE.md` с причиной. Проходу запрещено списывать такой отказ в мелочь. ## Стадия 2 — Сверка (обязательна во всех профилях) Два прохода, оба против **записанного** критерия. Машину не держат ни один, ребра между ними нет — уходят одним сообщением сразу после зелёного гейта, вместе со стадией 3 или 4 — той, что в профиле. - `review-specs` закрывает тему `requirements`. Критерий взят из **дельта-спек предлагаемого изменения**, а не из proposal, сообщения коммита или описания задачи. Сверка двунаправленная; направление `code → spec` важнее. - `review-code` закрывает тему `conventions` **и делает технический разбор кода** — это две его половины. Первая ищет дефект, который сработает сам, на обычном входе: необработанная ветка отказа, пустое значение, граница диапазона, перепутанный операнд, неосвобождённый ресурс, неверно применённый интерфейс библиотеки. Вторая сверяет с конвенциями проекта, беря только ту их часть, которая **не выражается правилом**: механизируемое уже проверила стадия 1. **Технический разбор — не тема, а обязанность прохода, и он единственный.** Остальные читают код как материал для своей оптики: `specs` — против требований, `basics` — против отказов окружения, `architecture` — против устройства. «Здесь ошибка в логике» не говорит больше никто, и до недавнего времени не говорил никто вовсе: `code` был проходом только по конвенциям, а дефект ловился разве что случайно. Это была самая крупная дыра конвейера, и стоила она дороже любой недосмотренной темы. Recall темы `conventions` равен длине конвенций проекта — это предел любой сверки, и ровно ради него существуют стадии 3 и 4. **Оба прохода на верхней модели, и по одной причине — цене пропуска.** У `specs` это направление `code → spec`: надо заметить **отсутствие** — тихий фолбэк, самодеятельный дефолт, проглоченную ошибку. У `code` это пропущенный дефект, который поедет в прод. Ни то ни другое не оставляет следа ни в отчёте, ни в границах покрытия; прочие опиниативные проходы держат `opus` из-за цены **ложных** находок, эти двое — из-за цены пропущенных. ## Стадия 3 — Темы (`quick`, `standard`; в `wide` — только свои темы проекта) Агент `review-basics`. Один проход, машину не держит, ничего не запускает и не меряет — уходит одним сообщением вместе со стадией 2, сразу после зелёного гейта. **Он не самостоятельная оптика, а держатель тем, у которых на этой ступени нет своего проходчика.** На `quick` и `standard` это `security`, `operations` и `architecture`: их именные проходы живут в `wide`, и без `basics` эти темы на большинстве задач не смотрел бы никто. На любой ступени, включая `wide`, он же — **приёмник проектных тем**: именных проходов конечное число, а тем столько, сколько заведёт проект. Глубина приходит из плана: **сверка** (открыть дом темы, открыть дифф, сравнить; потолок 2 находки) или **разбор** (построить сценарий рассуждением; потолок 4). Чего он не делает ни на какой глубине — замеров, эксперимента против драйвера, построенного пути, карты проекта, границы домена. Всё это стоит машины или входа шире диффа, то есть ровно того, ради чего существует `wide`. **Он покрывает миграцию и публичный контракт на нижних ступенях.** Это не побочный эффект, а условие, при котором миграция схемы вообще может не поднимать ступень: её шаг гоняет `gate`, спеку сверяет `specs`, а вопросы «обратима ли» и «что с записями новой версии после отката» задаёт здесь `basics`, темой `operations`. Уберёшь его — и нижние ступени останутся без единственного прохода, который смотрит на ось времени. **В `wide` он запускается только при своих темах проекта.** Нет таких — план говорит строкой «`basics` не запускается: все темы разобраны именными проходами». Это единственное место, где состав не выводится из профиля, и потому оно называется в плане явно. ## Стадия 4 — Доказательство (только `wide`) Три прохода, и все три уходят сразу после зелёного гейта, в одном ряду со стадией 2. Каждый берёт свою тему и доводит её до **доказательства**: - `review-adversary`, тема `security` — находка есть **построенный путь**, а не свойство: он пишет падающий тест и гоняет его; - `review-ops`, тема `operations` — постмортем от симптома у владельца сервиса к строке кода, с числами; - `review-architecture`, тема `architecture` — концептуальная целостность на входе шире диффа. **Первые двое помечены «держит машину», поэтому между ними ребро конфликта: они идут цепочкой, а не разом** (правило и его причина — в «Порядок прогона», раздел «Кто держит машину»). Направления у ребра нет: кто первый — неважно. `architecture` машину не держит и ждать ему нечего — он уходит в первой волне. Цепочка не отменяется общим «гони по графу» — она и есть часть графа. Отменяет её только прямое слово оператора про эту пару, и тогда в границы покрытия идёт строка, что числа прогона сняты под соседней нагрузкой. **Эта стадия зарабатывает больше всех остальных вместе — и она же дороже всех остальных вместе.** Измерено на пяти задачах подряд: враждебный проход дал пять из семи выживших находок дозапуска (включая обе верхние); эксплуатационный — единственный, кто нашёл, что откат бинаря поверх новой схемы стартует молча. Оба несут внешний оракул по построению: один обязан путь **прогнать**, второй смотрит ось времени и эксплуатации. Ровно поэтому они и стоят денег: оракул добывается запуском, а запуск — это машина, цепочка и часы. Раньше эта пара стояла в `standard`, то есть на большинстве задач. Стадия переехала в `wide` **сознательно и по цене, а не потому, что перестала находить**: она осталась самой ценной, но её ценность оплачивается на каждой задаче, а получается — на немногих. Что из-за этого перестало проверяться на нижних ступенях, названо в «Честном пределе» и обязано идти строкой в границы покрытия каждого прогона `quick` и `standard`. Дома тем приходят из плана разметчика: `security` — враждебному, `operations` (эксплуатация, хранилище, числа) — эксплуатационному, `architecture` (устройство, граница домена, решения) — архитектурному. Что с чем сшивать и почему — [project-facts.md](references/project-facts.md), раздел «Сшивать обязаны проходы». Без домов стадия вырождается в общие места. **Условие стадии и есть условие ступени `wide`:** изменение крупное или незнакомое. У архитектурного прохода работа появляется тогда, когда трогается несколько узлов разом или в проекте становится больше сущностей, чем было; у меряющей пары — когда форму решения нащупывали по ходу, и потому неизвестно, где она протекает. На мелкой правке вопрос «не появился ли второй способ» отвечается «нет» до запуска, а построенный путь строить негде. `review-architecture` получает **вход шире диффа**: дерево пакетов с назначением, граф внутренних зависимостей, инвентарь существующих концепций. Команду, которая это готовит, даёт раздел команд `CLAUDE.md`; нет команды — проход собирает карту сам и говорит об этом в границах покрытия. Главный вопрос — концептуальная целостность и **второй способ** делать то, что уже делается. Он же и оправдывает проход: на задаче про пересборку архитектурный проход нашёл, что новый код был **вторым проигрывателем журнала** со своим порядком. Второй обязательный вопрос — **что опытный человек отсюда удалил бы**: слой с единственной реализацией, интерфейс ради мока, незапрошенная конфигурируемость, подстраховка поверх подстраховки. Потолок — 3 находки плюс секция «дешевле переделать до мерджа». ## Стадия 5 — Triage (обязательна) Агент `review-triage`. **Единственный сток графа и единственный, кто агрегирует.** Входящие рёбра — все запущенные проходы: пока хоть один не вернул отчёт, триаж не стартует. Получает сырые выводы всех проходов, `git diff`, режим и **план разметчика**; возвращает финальный отчёт. **План на входе у триажа — не формальность, а сверка.** Он единственный, кто видит и то, что размечено, и то, что пришло: «тем размечено шесть, отчёты покрывают пять» — находка о самом прогоне, и заметить её больше некому. Раньше он получал список запущенных проходов и потому мог сверить только состав; теперь сверяет **темы**, а тема, оставшаяся без отчёта, — это то, чего список проходов никогда не показывал. Отсюда же правило, которое иначе выглядит придиркой: **триаж на неполном графе не запускается**. Прогон, остановленный на полпути находкой «переделать форму», до стока не доезжает — его отчёт агрегировал бы половину и выглядел бы полным. Без триажа проходы дают порядка сорока замечаний при единицах существенных. Потребитель здесь — оркестратор, который **молча реализует** всё, что прочитал: цена нетриажированного отчёта — не потерянное время человека, а разросшийся от вкусовщины код. Порядок: дедупликация по причине → оракул для всего `critical`/`major` → понижение неподтверждённого до гипотезы → отсев вкусовщины → ранжирование по ущербу × вероятности → потолок 7 пунктов в основном списке. ## Профиль `design` — до кода Запускается на шаге ревью спек (шаг 4 скилла `av-dev-pipeline:task-pipeline`), когда change уже имеет `proposal.md` и дельта-спеки, но кода ещё нет. **Состав здесь тоже не постоянный, и условие то же самое, что у `wide`:** изменение крупное или незнакомое. - **всегда** — `review-specs` в режиме «дизайн ДО кода». Дельта-спеки сверяются на каждой задаче: это самый дешёвый чекпоинт конвейера, и он ловит то, что на готовом коде уже не чинят; - **при крупном или незнакомом** — плюс `review-rubric` (фаза 1 без фазы 2: рубрика на задуманный узел становится приёмочными критериями и уезжает в `tasks.md`) и `review-architecture` на предложении: можно ли выразить существующими понятиями — **включая конструкции стандартной библиотеки**, — не появляется ли второй способ. Вопрос «не изобретаем ли то, что уже есть в библиотеке» живёт здесь; тогда же задаётся вопрос автору дизайна: **«предложи три формы решения и назови компромисс каждой»** — если ответ показывает, что рассматривалась одна, это находка. Причина условия — арифметика, а не экономия на осторожности. Чекпоинт стоит **на каждой задаче**, поэтому три прохода здесь умножаются на число задач, и при мелкой нарезке это самая большая статья конвейера. Рубрика же на узел знакомого рода порождает свойства уже существующего рода — те, что и так записаны конвенциями и спеками; а `architecture` на мелкой правке отвечает «нет» на свой главный вопрос ещё до запуска (см. «Стадия 4»). **Граф этого профиля свой, и он плоский.** Гейта нет — кода ещё нет, запускать нечего; разметка сводится к одному вопросу (крупное или незнакомое), и его задаёт вызывающий вместе с профилем; машину не держит ни один проход; сток — не триаж, а шаг 5 пайплайна задачи, где замечания отрабатываются правкой спек. Триаж здесь не нужен: находок единицы, и каждая либо правит спеку, либо становится развилкой. ```mermaid flowchart TD proposal["предложение: proposal.md + дельта-спеки"] specs["specs (режим «дизайн ДО кода») — всегда"] novelty{{"изменение крупное
или незнакомое?"}} rubric["rubric, фаза 1 → приёмочные критерии в tasks.md"] arch["architecture на предложении"] author["вопрос автору: три формы решения и компромисс каждой"] fix["шаг 5 пайплайна: правка спек, развилки — вопросом в запись"] proposal --> specs proposal --> novelty novelty -->|да| rubric novelty -->|да| arch novelty -->|да| author novelty -->|нет| fix specs --> fix rubric --> fix arch --> fix author --> fix ``` Смысл профиля: архитектурная находка на готовом коде стоит переписывания и поэтому игнорируется; та же находка на предложении стоит абзаца обсуждения. `rubric` живёт **только** в этом профиле. Судить код по критерию, под который он писался, — корреляция по построению; те же 8–12 свойств уже лежат приёмочными критериями в `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/`. Он единственное, по чему потом видно, что было найдено и что из этого не заведено: нулевой урожай при непустом отчёте виден сразу. **Вместе с изменением он и переезжает:** после `opsx:archive` его адрес — `openspec/changes/archive//review/`. Кто ищет отчёт после архивации (батч на финальной сверке, приёмщик на сессии), смотрит **оба** пути; «отчёта нет» объявляется, только когда пуст и архивный, иначе самый дорогой сценарий «состав ревью неизвестен, гоняем заново» срабатывает на каждой доведённой задаче. ## Честный предел Модель воспроизводит медиану публичного кода, смещённую к популярному и туториальному: отсюда тяга к интерфейсам ради интерфейсов, лишним мокам и конфигурируемости, которую никто не просил. **«Идиоматично» и «распространено» — разные вещи**; проходы обязаны различать их и опираться на поимённое положение руководства, а не на ощущение частотности. Согласие нескольких проходов — **не подтверждение**: это один источник, высказавшийся несколько раз. Совпадение повышает приоритет, но не `confidence`. Что недоступно **этому** проекту принципиально — перечисляет «Недоступно проверке» в `docs/review.*`, по темам, и оба его подраздела целиком уезжают в границы покрытия. **Тема, у которой нет дома, — тоже граница покрытия**, и она объявляется планом на каждом прогоне, а не разово. Независимо от проекта недоступно: - поведение внешних систем в их будущих версиях; - реальный профиль нагрузки; и то, что на самом деле лежит в данных, — **сверх того, что снято с провенансом в `docs/research/`**; - завязка внешних потребителей на текущую форму ответа; - суждение «этой функциональности не должно существовать». Отдельно и честно: **поимённая сверка с положениями руководств по стилю языка не задаётся ни одним проходом.** Проход про идиоматичность упразднён, его способные части переселены (эксперимент против поведения библиотеки и драйвера — в `ops`, вопрос 8; «не изобретаем ли то, что уже есть в библиотеке» — в `architecture`, вопрос 1), но различение «идиоматично против распространено» теперь не спрашивает никто. Класс обратимый — портит форму кода, не данные, — и его надо признавать в границах покрытия, а не считать проверенным. **На `quick` и `standard` не проверяется ничего, что требует запуска.** Это самая крупная граница покрытия конвейера, и она обязана идти строкой в каждом таком прогоне — поимённо, а не общим «профиль ниже». Не проверяется: построенный путь атаки (его надо прогнать), поведение библиотеки и драйвера в вырожденном случае (достаётся только экспериментом), любое число — время удержания блокировки, пик кучи, темп роста журнала, стоимость на годовой истории. `basics` задаёт часть тех же вопросов **чтением**, и его ответы поэтому слабее: он формулирует условиями, оракула не приносит и выше гипотезы находку не поднимает — кроме той, что опирается на инвариант `CLAUDE.md`. **Темы при этом закрыты все — разница в глубине, и её надо читать буквально.** «Тема `security`, глубина сверка» не значит «безопасность проверена»: значит, что дом темы открыли, дифф посмотрели и сравнили. Между сверкой и доказательством лежит весь класс дефектов, который виден только построенным путём, — и он проверяется на 5–10% задач. Это сознательная сделка, а не пробел в устройстве: цена ступени `wide` платится на каждой задаче, а окупается на немногих. Проверяется сделка не рассуждением, а журналом дефектов: если класс, который ловят только меряющие проходы, начал всплывать после мерджа — ступень выбирают слишком низко. Так же честно и про упразднённый проход: **«не знаю, чего не знаю» больше не достаёт никто.** Проход независимой реализации писал свою версию узла, не открывая существующую, и диффил по решениям — декомпозиция, владение данными, модель конкурентности, форма решения там, где спека выбора не сделала. Он снят по решению оператора о **стоимости** — счёт определялся объёмом вывода, и на прогон он тратил больше всех остальных проходов вместе, — а не по замеру, который [calibration.md](references/calibration.md) требует перед удалением. Значит и записывается это как сознательное сужение, а не как «класс оказался пустым»: остаток независимого взгляда даёт профиль `design` (код пишется под его находки) и `architecture` (второй способ, лишние слои), но **альтернативной реализации, с которой можно сдиффить решения, у конвейера теперь нет**. Класс идёт строкой в границы покрытия каждого прогона — там же, где проект перечисляет своё в подразделе «перестали проверять сознательно». Это и есть причина, по которой конвейер готовит ревью, а не заменяет его. ## Ссылки - [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) — журнал проскочивших дефектов.