--- name: review-pipeline description: Конвейер ревью изменений jellybit — детерминированный гейт, сверка с дельта-спеками OpenSpec в обе стороны, generative-проходы (рубрика, независимая реализация, stdlib grounding, negative space), архитектура, враждебные постановки и обязательный триаж. Вызывается из task-pipeline (чекпоинты ревью), task-batch (финальная сверка) и отдельно — профилем design на OpenSpec-предложении ДО кода. --- # Конвейер ревью (jellybit) Готовит ревью — **не заменяет его**. Потребитель отчёта — оркестратор, который чинит код; человек читает только сводку, развилки и границы покрытия. ## Три правила, из которых всё следует Если ситуация не покрыта инструкцией — решай по ним. 1. **Recall чек-листа равен длине чек-листа.** Проход, устроенный как «проверь пункты 1..N», найдёт ровно перечисленное. Всё неявное — идиомы, форма решения, «так не делают» — неперечислимо по определению: перечислимое уже стало бы конвенцией. Отсюда деление проходов на **applicative** (применяют заданный критерий) и **generative** (сперва порождают критерий или альтернативу, потом сравнивают). Расширять чек-листы бесполезно; неявный слой достают только generative-проходы. 2. **Ценность верификатора = наличие внешнего оракула × декорреляция с автором**, а не число ролей. Под всеми ролями одна модель с одними априорными, вход у всех общий: седьмая роль почти не добавляет recall, но линейно удорожает триаж. Иерархия надёжности: детерминированный инструмент > агент, который его **запускает** и интерпретирует вывод > агент с чистым мнением. Максимум работы переносим вниз. 3. **Отчёт без границ покрытия хуже отсутствия отчёта.** «Критичных проблем не обнаружено» потребляет ощущение проверенности, ничего не гарантируя. Секция границ покрытия обязательна и не сокращается — в том числе в докладе человеку. ## Профили | Профиль | Когда | Стадии | |---|---|---| | `quick` | багфикс, локальная правка, доки | 0, 1, 5 | | `standard` | новая функциональность в существующем модуле | 0, 1, 2, 5 | | `deep` | новый модуль/пакет, изменение публичного контракта, миграция БД, трогает инварианты безопасности данных | 0, 1, 2, 3, 4, 5 | | `design` | **до кода**, на OpenSpec-предложении | rubric + idiom + architecture (см. ниже) | Правило выбора — по факту изменения, не по ощущению важности: - есть миграция в `internal/store/migrations/`, новый пакет `internal/*`, изменение сигнатуры публичной команды воркера или трогается раскладка файлов/пути → `deep`; - иначе меняется поведение, видимое снаружи (эндпоинт, htmx-путь, состояние загрузки, формат сообщения бота) → `standard`; - иначе → `quick`. Профиль объявляется в отчёте. Понижение профиля — решение оркестратора, и оно попадает в границы покрытия строкой «профиль понижен до X, потому что …». ## Стадия 0 — Gate (обязательна во всех профилях) Агент `jellybit-review-gate`. Запускает `task gate` и интерпретирует вывод. **Пока гейт красный — опиниативные проходы не запускаются.** Оркестратор чинит и перезапускает гейт. Исключение одно: отказ, унаследованный от базовой ветки (гейт проверяет это прогоном на базе) — тогда он фиксируется находкой и не блокирует. Гейт возвращает не только «зелено/красно», но и находки класса **отсутствующая верификация**: изменённые строки без покрытия, конкурентность без теста с параллельным доступом, флаки-тест (не ниже `major`), недоступный инструмент. Шаги выбираются по изменённым файлам: правка документации не гоняет тесты, линтеры и `-race`. Пропуск при этом не молчит — он виден в сводке с причиной и уезжает в границы покрытия, как и любой другой `SKIP`. ## Стадия 1 — Conformance (обязательна во всех профилях) Два applicative-прохода: оба применяют **записанный** критерий, оба дешёвые, запускаются **одним сообщением параллельно**. - `jellybit-review-specs` — критерий взят из **дельта-спек change в `openspec/changes//specs/`**, а не из proposal, сообщения коммита или описания задачи. Сверка двунаправленная; направление `code → spec` важнее. - `jellybit-review-code` — критерий взят из `docs/conventions/*.md`, и только та его часть, которая **не выражается правилом**: механизируемое уже проверила стадия 0. Уровень лога по адресату, единственный логирующий чокпоинт, новая ветвь отказа в `httpapi.classifyErr`, транзиентный ответ против персистентной диагностики, `ident.Parse` на входной границе, htmx-партиалы. Recall обоих равен длине их источника — это и есть предел applicative-проходов, ради которого существует стадия 2. ## Стадия 2 — Tacit layer (generative; `standard`, `deep`) Четыре прохода, каждый в своём контексте, запускаются **одним сообщением параллельно**: - `jellybit-review-rubric` — порождает рубрику до чтения кода, потом судит по ней; - `jellybit-review-reimpl` — пишет свою реализацию, не открывая существующую, затем диффит по решениям (в профиле `standard` включается только если изменение содержит новый файл или функцию длиннее ~60 строк — иначе дорог и бесполезен); - `jellybit-review-idiom` — заземляет «идиоматичность» на stdlib и поимённые положения гайдов; - `jellybit-review-negative` — чего нет и что лишнее. ## Стадия 3 — Global (`deep`, `design`) Агент `jellybit-review-architecture`. Получает **вход шире диффа**: дерево пакетов с назначением, граф внутренних зависимостей, инвентарь существующих концепций проекта. Готовит вход команда: ``` task review:context > tmp/review-context.md ``` Главный вопрос — концептуальная целостность и **второй способ** делать то, что уже делается. Потолок — 3 находки плюс секция «дешевле переделать до мерджа». ## Стадия 4 — Adversarial и operational (`deep`) `jellybit-review-adversary` (находка = построенный путь, не свойство) и `jellybit-review-ops` (постмортем от симптома у владельца сервиса к строке). Запускаются параллельно со стадией 2, если профиль `deep`. ## Стадия 5 — Triage (обязательна) Агент `jellybit-review-triage`. Единственный, кто агрегирует. Получает сырые выводы всех проходов и `git diff`; возвращает финальный отчёт. Без триажа шесть проходов дают порядка сорока замечаний при единицах существенных. Потребитель здесь — оркестратор, который **молча реализует** всё, что прочитал: цена нетриажированного отчёта — не потерянное время человека, а разросшийся от вкусовщины код. Порядок: дедупликация по причине → оракул для всего `critical`/`major` → понижение неподтверждённого до гипотезы → отсев вкусовщины → ранжирование по ущербу × вероятности → потолок 7 пунктов в основном списке. ## Профиль `design` — до кода Запускается на шаге ревью спек (`task-pipeline` шаг 4), когда change уже имеет `proposal.md` + дельта-спеки, но кода ещё нет. Состав: 1. `jellybit-review-specs` в режиме «дизайн ДО кода» — как раньше; 2. `jellybit-review-rubric`, фаза 1 без фазы 2: рубрика на задуманный узел становится приёмочными критериями и уезжает в `tasks.md`; 3. `jellybit-review-idiom` по описанию решения (какие конструкции stdlib закрывают задачу; не изобретаем ли то, что уже есть); 4. `jellybit-review-architecture` на предложении: вводит ли change новое понятие, можно ли выразить существующими, не появляется ли второй способ; 5. вопрос автору дизайна: **«предложи три формы решения и назови компромисс каждой»** — если ответ показывает, что рассматривалась одна, это находка. Смысл профиля: архитектурная находка на готовом коде стоит переписывания и поэтому игнорируется; та же находка на предложении стоит абзаца обсуждения. ## Контракт находок Единый для всех проходов — [references/finding-contract.md](references/finding-contract.md). Коротко: заголовок через **последствие**, обязательные поля `Файл`, `Severity`, `Confidence`, `Оракул`, `Последствие`, `Предложение`, `Найдено проходом`. `critical` без оракула или построенного пути не существует. Находка без поля «Последствие» не выводится вовсе. Каждый проход завершает вывод блоком `## Coverage of this pass`. ## Что происходит с находками дальше - Оркестратор чинит помеченное `Действие: инлайн` и **не логирует мелочь**. - `Действие: развилка` — на человека через `AskUserQuestion`, вопросом с вариантами. - `Promote candidates` — по процедуре [references/promote.md](references/promote.md): находка → конвенция → правило линтера → **удаление из конвенций и из промптов**. Третий шаг обязателен. - Дефект, проскочивший ревью и всплывший позже, идёт в [docs/review/journal.md](../../../docs/review/journal.md) — сразу, не ретроспективно: теряется именно причина непоймания. ## Честный предел Модель воспроизводит медиану публичного Go, смещённую к популярному и туториальному: отсюда тяга к интерфейсам ради интерфейсов, лишним мокам и конфигурируемости, которую никто не просил. **«Идиоматично» и «распространено» — разные вещи**; проходы обязаны различать их и опираться на поимённое положение гайда, а не на ощущение частотности. Согласие нескольких проходов — **не подтверждение**: это один источник, высказавшийся несколько раз. Совпадение повышает приоритет, но не `confidence`. Ни одному проходу принципиально недоступно: - история инцидентов на umbar и то, что уже ломалось в проде; - поведение таблицы SQLite под реальным объёмом и профилем нагрузки; - завязка внешних потребителей (Jellyfin, бот, закладки) на текущее поведение; - суждение «этой фичи не должно существовать». Это и есть причина, по которой конвейер готовит ревью, а не заменяет его. ## Ссылки - [references/finding-contract.md](references/finding-contract.md) — контракт находок. - [references/promote.md](references/promote.md) — промоут находка → конвенция → правило → удаление. - [references/calibration.md](references/calibration.md) — калибровка инъекцией, вердикты keep/retune/drop. - [references/migration-2026-07.md](references/migration-2026-07.md) — отчёт «было → стало» по переработке конвейера. - [docs/review/journal.md](../../../docs/review/journal.md) — журнал проскочивших дефектов.