Полный набор гонялся чаще, чем оправдано, и размер задач тут вторая причина, не первая. Первая — триггеры: миграция схемы, публичный контракт и инвариант поднимали ступень, не добавляя ни одного прохода. Миграцию гоняет gate шагом миграций и разбирает ops, контракт сверяет specs направлением code→spec, инвариант даёт основание для critical любому проходу — все трое уже в standard. На проекте с базой и эндпоинтами верхняя ступень оказывалась не исключением, а умолчанием: правило объявляло исключением то, что происходит всегда. Теперь ступень поднимает то, что даёт работу новому проходу. wide означает ровно одно — изменение вводит новое понятие или структурную единицу; добавить поле в существующий ответ это не концепт. standard стал рабочим умолчанием. Проект, где изменение контракта и правда архитектурное, поднимает его сам в docs/review.md — уточнением, а не возвратом прежнего умолчания. Чекпоинт design получил то же условие: specs идёт всегда, rubric и architecture — только при новом понятии. Он стоит на каждой задаче, поэтому при мелкой нарезке три прохода умножаются на число задач. Со стороны задач — шов нарезки: тест декомпозиции отвечает, допустим ли разрез, шов отвечает, где его провести. Резать по границе, за которой падает ступень; не резать, когда обе половины остаются в одной — костяк из четырёх проходов платится за каждую задачу, и такой разрез делает ревью дороже. Порога в числе границ нет по тому же принципу, что в теме 16: размер не триггер. Дешёвое место заметить разнородную задачу — показ набора спринта, там «Затрагивает» уже написан, а предложение ещё не заведено. Правило выведено из состава проходов, а не из статистики прогонов — замер остаётся за обкаткой. DECISIONS 18, RRR–WWW и следствия 72–75; JJJ темы 17 помечен как пересмотренный. Шаг про «Триггеры профиля» дописан в ещё не выкаченную версию 3 канона, а не отдельной версией. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
662 lines
59 KiB
Markdown
662 lines
59 KiB
Markdown
---
|
||
name: review-pipeline
|
||
description: "Конвейер ревью изменения — детерминированный гейт, сверка с дельта-спеками в обе стороны, враждебные постановки и эксплуатационный постмортем, архитектурный проход, независимая реализация в верхнем профиле и обязательный триаж. Четыре ступени стоимости: quick, standard, wide, deep. Порядок прогона — граф зависимостей, а не очередь: гейт открывает опиниативные проходы, проходы с пометкой «держит машину» идут цепочкой, независимая реализация стоит за барьером стоимости, триаж — единственный сток. Линейный прогон — по слову оператора или на занятой машине. Проектная специфика приходит из документов канона av-dev-pm. Вызывается из task-pipeline (чекпоинты ревью), из task-batch (финальная сверка) и отдельно — профилем design на предложении ДО кода."
|
||
---
|
||
|
||
# Конвейер ревью
|
||
|
||
Готовит ревью — **не заменяет его**. Потребитель отчёта — оркестратор, который
|
||
чинит код; человек читает только сводку, развилки и границы покрытия.
|
||
|
||
## Три правила, из которых всё следует
|
||
|
||
Если ситуация не покрыта инструкцией — решай по ним.
|
||
|
||
1. **Recall чек-листа равен длине чек-листа.** Проход, устроенный как «проверь
|
||
пункты 1..N», найдёт ровно перечисленное. Всё неявное — идиомы, форма
|
||
решения, «так не делают» — неперечислимо по определению: перечислимое уже
|
||
стало бы конвенцией. Отсюда деление проходов на **applicative** (применяют
|
||
заданный критерий) и **generative** (сперва порождают критерий или
|
||
альтернативу, потом сравнивают). Расширять чек-листы бесполезно; неявный слой
|
||
достают только generative-проходы.
|
||
2. **Ценность верификатора = наличие внешнего оракула × декорреляция с
|
||
автором**, а не число ролей. Под всеми ролями одна модель с одними
|
||
априорными, вход у всех общий: седьмая роль почти не добавляет recall, но
|
||
линейно удорожает триаж. Иерархия надёжности: детерминированный инструмент >
|
||
агент, который его **запускает** и интерпретирует вывод > агент с чистым
|
||
мнением. Максимум работы переносим вниз.
|
||
3. **Отчёт без границ покрытия хуже отсутствия отчёта.** «Критичных проблем не
|
||
обнаружено» потребляет ощущение проверенности, ничего не гарантируя. Секция
|
||
границ покрытия обязательна и не сокращается — в том числе в докладе человеку.
|
||
|
||
## Предпосылки
|
||
|
||
Конвейер опирается на внешнюю обвязку и без неё работает не целиком. Проверь это
|
||
один раз, при установке плагина в проект:
|
||
|
||
- **OpenSpec — жёсткая предпосылка, а не опция.** Профиль `design`, проход
|
||
`review-specs` и
|
||
вызывающий пайплайн задачи завязаны на дельта-спеки
|
||
(`openspec/changes/<id>/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` | green | gate, code, ops | вход структурный, критерий записан заранее |
|
||
| `opus` | yellow | specs, adversary, rubric, reimpl | суждение без опоры на инструмент |
|
||
| `fable` | red | triage, architecture | ошибка распространяется дальше самой находки |
|
||
|
||
**Цвет charter'а кодирует модель, а не роль прохода.** Это единственное
|
||
назначение цвета: список агентов читается взглядом, и по нему сразу видно, чем
|
||
платит прогон. Роль прохода из имени и так понятна, а цвет, розданный по ролям,
|
||
не отвечает ни на один вопрос, который задают во время прогона. Раскладка живёт
|
||
здесь и **проверяется механически** — цвет ставится один раз при заведении
|
||
charter'а, а модель потом двигает калибровка, и разъезжаются они молча.
|
||
|
||
**Самая дорогая модель — только двум проходам, и это калибровка, а не
|
||
осторожность.** Замер: на первом же прогоне конвейера самые ценные находки дали
|
||
`opus`-проходы — сверка спек дала 13 находок с оракулами, а проход про
|
||
идиоматичность (впоследствии упразднённый) — три эксперимента против драйвера БД
|
||
с воспроизведёнными числами. Разницы в пользу более дорогой модели на
|
||
опиниативных проходах не обнаружилось — значит платить за неё там не за что.
|
||
|
||
Двое, у кого она остаётся, отобраны по одному признаку: **их ошибка
|
||
распространяется дальше собственной находки.**
|
||
|
||
- `triage` — через него проходит всё, что оркестратор реализует **молча**:
|
||
ложноположительная находка становится кодом, потерянный `critical` — дефектом.
|
||
Ошибка триажа дороже ошибки любого отдельного прохода.
|
||
- `architecture` — запускается только там, где изменение вводит новое понятие,
|
||
потолок в 3 находки делает его дешёвым по выходу, а находка на предложении
|
||
стоит абзаца против переписывания на готовом коде. Дёшево × высокое плечо.
|
||
|
||
`reimpl` намеренно **не** в этом списке, хотя он самый ценный из generative: его
|
||
стоимость определяется объёмом вывода (он пишет реализацию целиком), так что
|
||
дорогая модель множит самый большой счёт. Ценность же его — в **независимости**
|
||
взгляда, а не в мощности модели.
|
||
|
||
**Самая дешёвая модель не используется ни на одном проходе, и это не экономия
|
||
наоборот.** Дешёвая модель на опиниативном проходе даёт правдоподобные находки,
|
||
которые триаж обязан опровергать оракулом, — а это самая дорогая операция
|
||
конвейера. Механизируемая же работа здесь вынесена **ниже** модели: гейт,
|
||
покрытие диффа, карта проекта — это скрипты проекта, они стоят ноль токенов.
|
||
Дешёвому проходу просто не осталось работы.
|
||
|
||
Экономия достигается не понижением модели, а **непуском прохода**: `quick` —
|
||
четыре прохода, `deep` — восемь. Правило выбора профиля и есть главный
|
||
рычаг стоимости, и ступеней у него четыре именно поэтому.
|
||
|
||
## Профили
|
||
|
||
| Профиль | Когда | Стадии | Проходов |
|
||
|---|---|---|---|
|
||
| `quick` | багфикс, локальная правка, доки | 0, 1, 5 | 4 |
|
||
| `standard` | **рабочее умолчание**: поведение, миграция схемы, публичный контракт, инвариант | 0, 1, 2, 5 | 6 |
|
||
| `wide` | изменение вводит новое понятие или структурную единицу | 0, 1, 2, 4, 5 | 7 |
|
||
| `deep` | изменение вводит новое правило идентичности, слияния или разбора | 0, 1, 2, 3, 4, 5 | 8 |
|
||
| `design` | **до кода**, на предложении | specs, плюс rubric и architecture по условию `wide` | 1–3 |
|
||
|
||
**`wide` назван по тому, что он добавляет: вход шире диффа.** Единственное его
|
||
отличие от `standard` — архитектурный проход, а тот и получает дерево пакетов,
|
||
граф зависимостей и инвентарь понятий вместо одного диффа. Ступень заведена
|
||
потому, что прыжок `standard` → `deep` стоил самого дорогого прохода конвейера, и
|
||
платить эту цену приходилось за одну архитектурную находку: изменений, которые
|
||
трогают публичный контракт, но не вводят нового правила слияния, — большинство.
|
||
|
||
**Состав сверяется по этой таблице до коммита.** Реестр из трёх-восьми проходов
|
||
проверяется взглядом — и это единственная защита от промаха, который уже
|
||
случился: пропуск прохода **не отличим от прохода без находок** (гейт зелёный,
|
||
спеки сошлись, отчёт выглядит полным), а заметить его мог бы только триаж,
|
||
который сам заполняется тем, что ему подали. Отчёт обязан перечислять запущенные
|
||
проходы **поимённо и с исходом**; непущенный идёт строкой «не запускался» в
|
||
границы покрытия, а не отсутствует. Цена молчащего пропуска измерена: семь
|
||
находок и отдельная задача на их дозакрытие.
|
||
|
||
Правило выбора профиля — **по факту изменения, не по ощущению важности**:
|
||
|
||
- трогается правило, определяющее **идентичность, слияние или разбор** данных →
|
||
`deep`;
|
||
- иначе изменение вводит **новое понятие или структурную единицу**: новый пакет
|
||
или слой, новая точка входа, второй способ делать то, что уже делается, перенос
|
||
ответственности между узлами → `wide`;
|
||
- иначе меняется поведение, видимое снаружи, трогается схема, публичный контракт
|
||
или инвариант проекта → `standard`;
|
||
- иначе → `quick`.
|
||
|
||
**Ступень поднимает то, что даёт работу новому проходу, а не то, что кажется
|
||
рискованным.** Это правило вывода, по которому спорные случаи решаются без нового
|
||
списка: спроси, какому проходу изменение даёт работу, которой у него не было
|
||
ступенью ниже.
|
||
|
||
Оно же объясняет, почему миграция схемы и публичный контракт **не** поднимают
|
||
ступень, хотя выглядят опаснее прочего. Они не добавляют ни одного прохода:
|
||
миграцию гоняет `gate` шагом миграций и разбирает `ops` («миграция под живым
|
||
потоком», «частичный откат при двух версиях»), контракт сверяет `specs`
|
||
направлением `code → spec`, инвариант даёт основание для `critical` любому
|
||
проходу. Все трое уже в `standard`. Раньше эти три факта стояли триггерами
|
||
верхних ступеней, и на проекте с базой и эндпоинтами верхняя ступень оказывалась
|
||
не исключением, а умолчанием — то есть правило объявляло исключением то, что
|
||
происходит всегда. `architecture` же получает работу **не** от того, что контракт
|
||
изменился, а от того, что появилось новое понятие: добавленное поле в
|
||
существующем ответе — не концепт.
|
||
|
||
**Верхняя ступень и есть триггер независимой реализации** — раньше он был
|
||
условием *внутри* `deep`, и профиль от этого распадался на два разных прогона под
|
||
одним именем. Условие никуда не делось, оно просто переехало туда, где выбирается
|
||
профиль: изменение с новым правилом слияния — единственный случай, когда триаж
|
||
называл отсутствие `reimpl` дырой покрытия.
|
||
|
||
Что здесь считается новым понятием и что — правилом идентичности, проект может
|
||
уточнить в `docs/review.md`, разделе настройки конвейера. Это **уточнение**, а не
|
||
отмена: не записано — работает список выше. Проект, где изменение контракта и
|
||
правда архитектурное (публичный SDK, чужие потребители), там же поднимает его до
|
||
`wide` — и это уточнение, а не возврат прежнего умолчания.
|
||
|
||
### Профиль — максимум по поверхности, и отсюда размер задачи
|
||
|
||
Условия читаются сверху вниз, и **первое подошедшее отвечает за весь дифф**.
|
||
Профиль изменения это максимум по его поверхности, а не средневзвешенное: одна
|
||
строка в перечне границ задачи поднимает ступень всему остальному, включая ту
|
||
часть, которая сама по себе была бы `quick`.
|
||
|
||
Отсюда следствие, которое дороже любой настройки триггеров: **цена ревью растёт
|
||
быстрее размера задачи.** Крупная задача не просто даёт больше диффа — она с
|
||
высокой вероятностью зацепит верхнее условие и оплатит верхний профиль целиком.
|
||
|
||
Обратное тоже верно и тоже не бесплатно: у каждой задачи есть **несокращаемый
|
||
костяк из четырёх проходов** (гейт, спеки, код, триаж). Разрезать задачу, обе
|
||
половины которой остаются в одном профиле, — значит заплатить костяк дважды за ту
|
||
же проверку. Резать стоит там, где разрез **снимает дорогой проход с большей
|
||
части диффа**. Шов и правило нарезки живут у того, кто ведёт задачи, — скилл
|
||
`av-dev-pm:tasks`, его `references/split.md`. Пути туда конвейер не выносит: за
|
||
пределы своего плагина он ходит вызовом скилла, а не файлом.
|
||
|
||
Профиль объявляется в отчёте. Понижение профиля — решение оркестратора, и оно
|
||
попадает в границы покрытия строкой «профиль понижен до X, потому что …».
|
||
|
||
## Порядок прогона — граф, а не очередь
|
||
|
||
Профиль отвечает «какие проходы», порядок — «что кого ждёт». Стадии остаются
|
||
единицей **состава** (профиль набирается стадиями, см. таблицу выше), но порядок
|
||
задают **не их номера**: между стадиями 1–4 настоящих зависимостей нет — ни один
|
||
проход не читает вывод другого, — и очередь между ними была бы платой ни за что.
|
||
|
||
Рёбер три вида, и они разной природы. Путать их нельзя: первое про
|
||
**осмысленность** (на красном гейте опиниативный проход не о чем), второе про
|
||
**железо**, третье про **деньги**.
|
||
|
||
| Ребро | Смысл | Между кем |
|
||
|---|---|---|
|
||
| **зависимость** | B не стартует, пока A не закончил, потому что без A задание B не определено | гейт → все опиниативные; все проходы → триаж |
|
||
| **конфликт за ресурс** | A и B не держат машину одновременно; кто из них первый — неважно, направления у ребра нет | проходы, помеченные «держит машину» |
|
||
| **барьер стоимости** | дорогое не запускается, пока дешёвое не сказало, что форма изменения выживет | только `deep` |
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
gate["gate<br/>(стадия 0, держит машину)"]
|
||
specs["specs"]
|
||
code["code"]
|
||
adversary["adversary<br/>(держит машину)"]
|
||
ops["ops<br/>(держит машину)"]
|
||
architecture["architecture<br/>(wide, deep)"]
|
||
barrier{{"форма изменения выживает?"}}
|
||
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 -->|"quick, standard, wide: барьера нет"| triage
|
||
reimpl --> triage
|
||
architecture --> triage
|
||
```
|
||
|
||
Читается граф так: **всё, у чего входящие рёбра закрыты, уходит одним
|
||
сообщением**. В `standard` после зелёного гейта это три узла разом — `specs`,
|
||
`code` и первый из меряющей пары, — а второй меряющий идёт следом за первым. В
|
||
`wide` к этой тройке добавляется четвёртым `architecture`. В `quick` — `specs` и
|
||
`code` разом, и сразу триаж.
|
||
|
||
**Схема здесь старше прозы.** Она не иллюстрация к тексту, а сам алгоритм
|
||
планировщика; проза ниже объясняет рёбра и называет их цену. Разошлись — прав
|
||
граф, а расхождение чинится правкой текста.
|
||
|
||
**Ребро значит «A закончил раньше, чем B стартовал», и ничего больше.** В обычном
|
||
графе задач ребро тянет за собой данные — здесь нет, и это не деталь реализации.
|
||
Проход **не видит** находок других проходов, в каком бы порядке их ни запустили.
|
||
Вся ценность конвейера держится на декорреляции: под всеми ролями одна модель с
|
||
одними априорными, и стоит показать ей чужой вывод — она согласится. Согласие
|
||
нескольких проходов и так не повышает `confidence` (см. «Честный предел»);
|
||
согласие **наведённое** ещё и маскируется под независимое подтверждение.
|
||
Единственный, кто получает чужие выводы, — триаж, и это его работа.
|
||
|
||
### Кто держит машину
|
||
|
||
Ресурс один и неделимый: **машина** — тесты, поднятый сервис, СУБД, порты, диск.
|
||
Проходы, заявившие его, сериализуются между собой в любом профиле и на любой
|
||
стадии; порядок внутри цепочки произволен.
|
||
|
||
| Проход | Держит машину | Почему |
|
||
|---|---|---|
|
||
| `gate` | да | запускает инструменты проекта — но он источник графа и один по построению |
|
||
| `adversary` | да | находка есть **построенный путь**: он пишет падающий тест и гоняет его |
|
||
| `ops` | да | доказывает числами: время удержания блокировки, пик кучи, темп роста журнала |
|
||
| `triage` | да | проверяет оракул `critical`/`major` запуском — но он сток и тоже один |
|
||
| `specs`, `code`, `reimpl`, `architecture`, `rubric` | нет | читают и рассуждают; `reimpl` пишет свою реализацию в черновик, но не исполняет её |
|
||
|
||
**Правило про ресурс, а не про имена.** Раньше здесь стояло именованное
|
||
исключение «`adversary` и `ops`»; оно рассыпается, как только проход начнёт
|
||
мерить или в проекте появится свой. Два прохода на одной машине соревнуются за
|
||
диск, CPU и за саму СУБД и выдают числа, которые не воспроизведутся, — а число,
|
||
снятое под конкурентную нагрузку, это находка с испорченным оракулом. Её
|
||
опровержение стоит дороже всего выигрыша от параллельности, и она хуже
|
||
отсутствующей: выглядит доказанной. Правило выведено из находок, целиком
|
||
державшихся на таких замерах; у каждого проекта они свои и лежат в журнале
|
||
`docs/review.md`.
|
||
|
||
Проект вправе пометить «держит машину» и другой проход — в `docs/review.md`,
|
||
разделе настройки конвейера. Снимать пометку с перечисленных нельзя.
|
||
|
||
### Барьер стоимости — вместо раннего выхода
|
||
|
||
Барьер существует ровно там, где ранний выход зарабатывал: `reimpl` пишет
|
||
реализацию целиком и потому самый дорогой проход конвейера. Если дешёвая часть
|
||
нашла, что **форму изменения** надо переделывать, он будет писать её против кода,
|
||
которого через час не станет.
|
||
|
||
- **прошло без находок «переделать форму»** — барьер открыт, `reimpl` уходит;
|
||
- **есть такая находка** — прогон останавливается, находка чинится, конвейер
|
||
запускается **заново с нулевой стадии**, а не «доезжает» остатком по старому
|
||
коду. Незапущенные проходы идут в границы покрытия строкой «не запускался:
|
||
прогон остановлен на <проход> из-за <находка>», поимённо. Триаж на половине
|
||
прогона не запускается: его отчёт выглядит полным, потому что агрегирует всё,
|
||
что ему подали, — это тот же молчащий пропуск, что и в разделе «Профили»;
|
||
- **находка чинится в пределах существующей формы** (`Действие: инлайн`) —
|
||
барьер не срабатывает: дешевле дособрать все находки и починить пачкой, чем
|
||
гонять конвейер дважды.
|
||
|
||
**`architecture` стоит за барьером только там, где барьер и так есть.** В `deep`
|
||
он уходит вместе с `reimpl` — ждать ему всё равно нечего. В `wide` он стартует
|
||
сразу после зелёного гейта, в одном ряду со стадиями 1 и 2: своего барьера он не
|
||
заслуживает. Потолок в 3 находки делает его дешёвым, а барьер не бесплатен — он
|
||
сериализует то, что могло идти разом, и платить сериализацией за один дешёвый
|
||
проход не за что. Есть и вторая причина, помельче: барьер спрашивает «выживает ли
|
||
форма изменения», а `architecture` — как раз тот, кто на этот вопрос отвечает.
|
||
|
||
В `quick`, `standard` и `wide` барьера нет — за ним нечего защищать: стадии 3 в
|
||
этих профилях не бывает, и граф там плоский от гейта до триажа. Находка «переделать
|
||
форму» ловится в них триажем, а прогон после починки повторяется целиком: платить
|
||
за это нечем, дорогих проходов в этих профилях нет. В `design` его тоже
|
||
нет, и по другой причине: там предметом и является форма, а все три прохода
|
||
читают одно предложение — защищать нечего, у графа этого профиля своя форма (см.
|
||
его раздел).
|
||
|
||
### Линеаризация — когда графа мало
|
||
|
||
Граф можно вытянуть в одну цепочку. Это отступление, и оно называется в отчёте:
|
||
|
||
1. **сказал оператор** — «гони линейно». Набора называть не надо: линейный прогон
|
||
ничего не портит, он только дольше, и домысливать тут нечего;
|
||
2. **машина занята, и знает об этом вызывающий.** Рядом идёт другая задача,
|
||
поднят сервис, гоняется дорогая проверка проекта. Сам конвейер занятости
|
||
машины не видит — её обязан назвать тот, кто запускает; так и делает
|
||
`av-dev-pipeline:task-batch`, когда ведёт задачи параллельно;
|
||
3. **разбор самого конвейера** — когда выясняется, почему проход чего-то не
|
||
нашёл, порядок и изоляция важнее скорости.
|
||
|
||
Обратное отступление — **слить цепочку ресурса** (пустить меряющие проходы
|
||
разом) — бывает только по прямому слову оператора, и тогда в границы покрытия
|
||
идёт строка: какие проходы шли одновременно и что замеры этого прогона как
|
||
оракул слабее.
|
||
|
||
Режим объявляется в отчёте наравне с профилем: **`по графу`** — одним словом,
|
||
**`линейно`** — с причиной (какой именно из трёх).
|
||
|
||
## Стадия 0 — Gate (обязательна во всех профилях)
|
||
|
||
Агент `review-gate`. Запускает команду гейта из семантики гейта в `CLAUDE.md` и
|
||
интерпретирует вывод.
|
||
|
||
**Пока гейт красный — опиниативные проходы не запускаются.** Оркестратор чинит и
|
||
перезапускает гейт. Исключение одно: отказ, унаследованный от базовой ветки
|
||
(гейт проверяет это прогоном на базе) — тогда он фиксируется находкой и не
|
||
блокирует.
|
||
|
||
Гейт возвращает не только «зелено/красно», но и находки класса **отсутствующая
|
||
верификация**: изменённые строки без покрытия, конкурентность без теста с
|
||
параллельным доступом, флаки-тест (не ниже `major`), недоступный инструмент.
|
||
|
||
Шаги выбираются по изменённым файлам: правка документации не гоняет тесты,
|
||
линтеры и детектор гонок. Пропуск при этом не молчит — он виден в сводке с
|
||
причиной и уезжает в границы покрытия, как и любой другой `SKIP`.
|
||
|
||
Шаги, которые красят гейт безусловно, перечислены в `CLAUDE.md` с причиной. Проходу
|
||
запрещено списывать такой отказ в мелочь.
|
||
|
||
## Стадия 1 — Conformance (обязательна во всех профилях)
|
||
|
||
Два applicative-прохода: оба применяют **записанный** критерий, оба дешёвые.
|
||
Машину не держат ни один, ребра между ними нет — уходят одним сообщением сразу
|
||
после зелёного гейта, вместе со стадией 2, если она в профиле.
|
||
|
||
- `review-specs` — критерий взят из **дельта-спек предлагаемого изменения**, а не
|
||
из proposal, сообщения коммита или описания задачи. Сверка двунаправленная;
|
||
направление `code → spec` важнее.
|
||
- `review-code` — критерий взят из конвенций проекта, каталог
|
||
`docs/conventions/`. Берётся только та их часть, которая **не выражается
|
||
правилом**: механизируемое уже проверила стадия 0. Что именно механизировано,
|
||
перечисляет `conventions/README.md` — повторять это проходом вредно.
|
||
|
||
Recall обоих равен длине их источника — это и есть предел applicative-проходов,
|
||
ради которого существует стадия 2.
|
||
|
||
## Стадия 2 — Adversarial и operational (`standard`, `wide`, `deep`)
|
||
|
||
Два прохода:
|
||
|
||
- `review-adversary` — находка есть **построенный путь**, а не свойство;
|
||
- `review-ops` — постмортем от симптома у владельца сервиса к строке кода.
|
||
|
||
**Оба помечены «держит машину», поэтому между ними ребро конфликта: они идут
|
||
цепочкой, а не разом** (правило и его причина — в «Порядок прогона», раздел «Кто
|
||
держит машину»). Направления у ребра нет: кто первый — неважно. Со стадией 1 они
|
||
конфликта не имеют и стартуют одновременно с ней; ждать её незачем.
|
||
|
||
Цепочка не отменяется общим «гони по графу» — она и есть часть графа. Отменяет
|
||
её только прямое слово оператора про эту пару, и тогда в границы покрытия идёт
|
||
строка, что числа прогона сняты под соседней нагрузкой.
|
||
|
||
**Эта стадия зарабатывает больше всех остальных вместе, и потому стоит уже в
|
||
`standard`, а не только в верхних профилях.** Измерено на пяти задачах подряд: враждебный
|
||
проход дал пять из семи выживших находок дозапуска (включая обе верхние);
|
||
эксплуатационный — единственный, кто нашёл, что откат бинаря поверх новой схемы
|
||
стартует молча. Оба несут внешний оракул по построению: один обязан путь
|
||
**прогнать**, второй смотрит ось времени и эксплуатации, которую не смотрит
|
||
никто другой.
|
||
|
||
Материал берётся из документов: `docs/security.md` — враждебному,
|
||
`docs/architecture.md`, `docs/research/` и `docs/database.md` —
|
||
эксплуатационному. Что с чем сшивать и почему — [project-facts.md](references/project-facts.md),
|
||
раздел «Сшивать обязаны проходы». Без этих документов стадия вырождается в общие
|
||
места.
|
||
|
||
## Стадия 3 — Independent reimplementation (только `deep`)
|
||
|
||
Единственный проход, ради которого существует **барьер стоимости**, и
|
||
единственное, что отличает `deep` от `wide`.
|
||
|
||
- `review-reimpl` — пишет свою реализацию, не открывая существующую, затем
|
||
диффит по решениям. **Профиль и есть его условие:** `deep` выбирается ровно
|
||
тогда, когда изменение вводит новое правило идентичности, слияния или разбора
|
||
(проектная формулировка — в `docs/review.md`, если записана). Это самый дорогой
|
||
проход конвейера (его счёт определяется объёмом вывода — он пишет реализацию
|
||
целиком), а вне этого случая независимый взгляд в значительной мере уже дал
|
||
профиль `design`: код писался под его находки. Условие выбрано по факту:
|
||
единственный раз, когда триаж назвал отсутствие `reimpl` дырой покрытия, — это
|
||
была задача с новым правилом слияния сущностей.
|
||
|
||
Раньше это условие стояло **внутри** профиля, и `deep` означал то семь проходов,
|
||
то восемь. Реестр состава, который «проверяется взглядом», проверять было нечем:
|
||
у профиля не было одного правильного ответа. Теперь ступеней две — `wide` и
|
||
`deep`, — и у каждой состав ровно один.
|
||
|
||
## Стадия 4 — Global (`wide`, `deep`, `design`)
|
||
|
||
Агент `review-architecture`. В `deep` стоит **за барьером стоимости** (ждать ему
|
||
там всё равно нечего), в `wide` и `design` — в первой волне, сразу после старта
|
||
профиля. Машину не держит, с `reimpl` конфликта не имеет: за барьером они уходят
|
||
разом.
|
||
|
||
**Условие этой стадии и есть условие ступени `wide`:** изменение вводит новое
|
||
понятие или структурную единицу. Не «изменение крупное» и не «изменение опасное»:
|
||
у прохода появляется работа ровно тогда, когда в проекте становится больше
|
||
сущностей, чем было, — и тогда осмысленны оба его вопроса. На изменении, которое
|
||
ничего не вводит, вопрос «не появился ли второй способ» отвечается «нет» до
|
||
запуска, а вопрос «что опытный человек отсюда удалил бы» вырождается во
|
||
вкусовщину, которую потом отсеивает триаж.
|
||
|
||
Получает **вход шире диффа**: дерево пакетов с
|
||
назначением, граф внутренних зависимостей, инвентарь существующих концепций.
|
||
Команду, которая это готовит, даёт раздел команд `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{{"вводит новое понятие<br/>или структурную единицу?"}}
|
||
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/<id>/review/`.
|
||
Он единственное, по чему потом видно, что было найдено и что из этого не
|
||
заведено: нулевой урожай при непустом отчёте виден сразу.
|
||
**Вместе с изменением он и переезжает:** после `opsx:archive` его адрес —
|
||
`openspec/changes/archive/<id>/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) — журнал проскочивших дефектов.
|