av-dev-pipeline стал av-dev-code, review-pipeline — review
Имя описывало устройство, а не предмет: «пайплайн» говорит, что внутри конвейер, — а плагин занят кодом по задачам, и с появлением чекпоинтов он уже не конвейер в чистом виде. Набор имён стал параллельным: docs / tasks / code / git, каждое называет материал. Заодно review-pipeline стал review — слово ушло из плагина целиком, а не наполовину; скиллы выровнялись: resolve / review / openspec. Журнал версий канона переписан вместе со всеми, DECISIONS.md — нет. Разрез по типу высказывания, а не файла: наблюдение и причина неприкосновенны, предписание и адрес обязаны оставаться исполнимыми. Запись версии 10 велит «проверить, что плагин av-dev-pipeline установлен» — проект, дошедший до неё, выполнил бы невыполнимое.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,108 @@
|
||||
# Калибровка проходов
|
||||
|
||||
Без измерения набор проходов растёт монотонно и вырождается в театр: каждый
|
||||
кажется полезным, потому что иногда что-то говорит. Калибровка отвечает на
|
||||
единственный вопрос — **ловит ли проход дефект своего класса**.
|
||||
|
||||
## Процедура (инъекция дефекта)
|
||||
|
||||
1. Взять **реальный коммит** из истории (`git log --oneline`), лучше
|
||||
архивированный change с непустым диффом.
|
||||
2. Внести в него **один** дефект того класса, который проход обязан ловить по
|
||||
своему charter'у. Дефект должен быть правдоподобным — таким, какой реально
|
||||
пишет модель, а не карикатурой (`panic("TODO")` не считается).
|
||||
3. Прогнать **только этот проход** на подготовленном диффе — **три раза**,
|
||||
каждый в чистом контексте.
|
||||
4. Зафиксировать: нашёл `n/3`, число находок всего, число ложных.
|
||||
5. Вердикт:
|
||||
|
||||
| Результат | Вердикт | Что делаем |
|
||||
|---|---|---|
|
||||
| нашёл 3/3 или 2/3, ложных немного | `keep` | ничего |
|
||||
| нашёл 1/3 или 0/3 | `retune` | правим charter — сужаем вход, убираем чек-лист, добавляем оракул |
|
||||
| `retune` уже был дважды подряд | `drop` | удаляем проход |
|
||||
| находит, но ложных больше трети от всех находок | `retune` | триаж съедает больше, чем экономит проход |
|
||||
|
||||
Вердикты образуют храповик со счётчиком — его-то таблица и не показывает:
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
state "проход в составе метки" as live
|
||||
state "retune №1 — правка charter'а" as r1
|
||||
state "retune №2 — последняя попытка" as r2
|
||||
state "проход удалён" as dead
|
||||
|
||||
[*] --> live: заведён и откалиброван ДО включения
|
||||
live --> r1: 1/3, 0/3 или ложных больше трети
|
||||
r1 --> live: замер keep — счётчик сброшен
|
||||
r1 --> r2: снова не ловит
|
||||
r2 --> live: замер keep — счётчик сброшен
|
||||
r2 --> dead: снова не ловит — это театр
|
||||
```
|
||||
|
||||
Схема — **сводка** к таблице вердиктов выше: она добавляет только счётчик, и при
|
||||
расхождении прав таблица.
|
||||
|
||||
**`retune` не более двух раз подряд.** Проход, не находящий дефект своего класса
|
||||
в 2 из 3 прогонов после двух правок промпта, — это театр. Удалять, а не
|
||||
бесконечно править формулировки: каждая итерация правки промпта стоит дороже,
|
||||
чем отсутствие прохода.
|
||||
|
||||
**Существующий проход не удаляется без замера.** Сначала калибровка, потом
|
||||
решение — иначе удаляется то, что работало, а остаётся то, что громче. Обратный
|
||||
пример уже был: проход про идиоматичность стоял в списке на удаление как
|
||||
«вкусовщина», а замер показал, что он зарабатывает **экспериментами против
|
||||
поведения библиотеки и драйвера**, — и находка, воспроизведённая числом, отменила
|
||||
решение, принятое по ощущению.
|
||||
|
||||
## Состав проходов принадлежит плагину, а не проекту
|
||||
|
||||
Проходы общие. Проект не может удалить проход — он может **не звать** его, и
|
||||
тогда это идёт строкой «не запускался» в границы покрытия, как любой другой
|
||||
пропуск. Молча сузить состав нельзя: пропуск прохода не отличим от прохода без
|
||||
находок.
|
||||
|
||||
Отсюда два следствия:
|
||||
|
||||
- **правка charter'а — правка для всех проектов.** Прежде чем сужать
|
||||
формулировку под свою боль, проверь, не место ли ей в документах проекта: предмет проверки
|
||||
живёт там, метод — в charter'е;
|
||||
- **удаление прохода из плагина требует замера на двух проектах**, а не на одном:
|
||||
класс, не всплывший здесь, мог быть единственным работающим там.
|
||||
|
||||
## Пробы дефектов по проходам
|
||||
|
||||
Проба — заготовка инъекции. Список пополняется из журнала проскочивших дефектов
|
||||
(см. [review-journal.md](review-journal.md)): реальный проскочивший дефект —
|
||||
лучшая проба, какая вообще возможна, потому что синтетические смещены в сторону
|
||||
тех, которые уже умеешь придумывать.
|
||||
|
||||
| Проход | Класс дефекта для инъекции | Заготовка пробы |
|
||||
|---|---|---|
|
||||
| `review-scope` | пропущенная тема | положить в `docs/` новый документ и проверить, попал ли он в план темой |
|
||||
| `review-autotests` | отсутствующая верификация | убрать тест на изменённую ветку, оставить код рабочим |
|
||||
| `review-specs` | поведение вне спеки | добавить незаказанный фолбэк-дефолт на пустом входе |
|
||||
| `review-code` | нарушение прозаической конвенции | увести штатный отказ мимо единой точки трансляции ошибки |
|
||||
| `review-code` | технический дефект | не проверить возвращённую ошибку в ветке раннего возврата |
|
||||
| `review-rubric` | нарушенное свойство узла | у клиента внешнего сервиса убрать таймаут и протяжку `context` |
|
||||
| `review-basics` | отказ, видимый чтением | убрать обработку ошибки записи так, чтобы отказ считался успехом |
|
||||
| `review-basics` | своя тема проекта | нарушить правило из документа, у которого нет именного прохода |
|
||||
| `review-architecture` | второй способ | завести вторую точку генерации id мимо единой |
|
||||
| `review-adversary` | построенный путь | принять внешний идентификатор без разбора до запроса в хранилище |
|
||||
| `review-ops` | деградация окружения | убрать обработку недоступности внешней зависимости в фоновом цикле |
|
||||
| `review-triage` | шум | подать 20 находок, из них 15 вкусовщина и 3 дубля — проверить потолок и дедуп |
|
||||
|
||||
Метрик сверх этого не заводим. Precision, корреляция между проходами, стоимость
|
||||
прогона в токенах — всё это красиво звучит и никем не считается вручную; набор
|
||||
показателей, который не собирают, создаёт впечатление измеряемости и тем вреден.
|
||||
Работает ровно один механизм: инъекция дефекта и вердикт. Если корреляция двух
|
||||
проходов действительно бросается в глаза — это видно по полю `Найдено проходом`
|
||||
в триажированных отчётах и без отдельной метрики.
|
||||
|
||||
## Когда калибровать
|
||||
|
||||
- при заведении нового прохода — **до** включения в состав метки по умолчанию;
|
||||
- при правке charter'а существующего — иначе непонятно, правка помогла или нет;
|
||||
- при появлении записи в журнале проскочивших дефектов — калибруем тот проход,
|
||||
который должен был поймать;
|
||||
- планово — нет. Календарная калибровка ради галочки сама превращается в театр.
|
||||
@@ -0,0 +1,103 @@
|
||||
# Контракт находок
|
||||
|
||||
Единый формат для всех проходов конвейера ревью. Проход, нарушивший контракт,
|
||||
считается сломанным — триаж вправе выбросить его вывод целиком.
|
||||
|
||||
## Форма находки
|
||||
|
||||
```
|
||||
### <краткая формулировка ПОСЛЕДСТВИЯ, не симптома>
|
||||
- Файл: internal/<пакет>/<файл>.go:120-134
|
||||
- Severity: critical | major | minor | nit
|
||||
- Confidence: high | medium | low
|
||||
- Оракул: <падающий тест / команда с выводом / положение руководства / нет>
|
||||
- Последствие: <что произойдёт и при каких условиях>
|
||||
- Предложение: <конкретное изменение>
|
||||
- Найдено проходом: <имя агента; у проходов с раздельными потолками — имя и половина, например `code/техника`>
|
||||
```
|
||||
|
||||
## Правила
|
||||
|
||||
- **Заголовок через последствие.** Не «нет проверки токена», а «читатель без
|
||||
токена выгрузит всю историю». Не «слияние перезаписывает запись», а «повторная
|
||||
доставка сотрёт поля у уже сохранённой записи, и восстановить их нечем».
|
||||
Симптом в заголовке — это заявка на то, что читатель сам достроит последствие;
|
||||
он не достроит, он просто починит симптом.
|
||||
- **`critical` без оракула или построенного пути не существует.** Оракул — это
|
||||
падающий тест, вывод выполненной команды или поимённое положение руководства. Не
|
||||
«вероятно, здесь гонка», а прогон детектора гонок с его выводом.
|
||||
- **`confidence: low` — это «так обычно пишут».** Такие находки допустимы, но не
|
||||
поднимаются выше `minor`. Частотность конструкции в публичном коде — не
|
||||
аргумент.
|
||||
- **Находка без поля «Последствие» не выводится вовсе.** Пустое «Последствие:
|
||||
ухудшает читаемость» равносильно отсутствию поля.
|
||||
- **`nit` допустим только при нарушении записанной конвенции** — со ссылкой на
|
||||
файл и раздел конвенций проекта (`docs/conventions/`) либо на
|
||||
правило линтера. Если правило механизируемо, но не механизировано — это не
|
||||
находка ревью, это `Promote candidate` (см. [promote.md](promote.md)).
|
||||
- **`critical` по основанию «нарушен инвариант проекта» требует инвариантов.**
|
||||
Ссылка идёт на пункт раздела инвариантов `CLAUDE.md` дословно. Без них основание
|
||||
недоступно — см. [project-facts.md](project-facts.md), поразрядная деградация.
|
||||
- **Расхождение — не дефект, пока не названо последствие.** Особенно для
|
||||
архитектурного прохода: «я бы сделал иначе» без последствия не выводится.
|
||||
|
||||
## Шкала severity
|
||||
|
||||
| Severity | Что это | Пример |
|
||||
|---|---|---|
|
||||
| `critical` | нарушение инварианта проекта, потеря или порча данных, утечка секрета, построенный путь к отказу | запись потеряна при слиянии; тело пользовательской выгрузки в поле лога |
|
||||
| `major` | сломанное требование дельта-спеки, необрабатываемый отказ штатного сценария, флаки-тест, поведение вне спеки, меняющее исход | приём отвечает 200, не записав тело: доставка считается принятой, а данных нет |
|
||||
| `minor` | отступление от конвенции с реальной ценой, отсутствующая наблюдаемость, дублирование, которое разойдётся | ни одного чекпоинта на пути разбора: молчащая автоматизация неотличима от пустого потока |
|
||||
| `nit` | нарушение записанной конвенции без последствий за пределами чтения | `msg` с интерполяцией вместо константы |
|
||||
|
||||
Шкала привязана к обратимости, а не к громкости: класс «необратимо и молча»
|
||||
всегда весит больше класса «шумно и лечится повтором». Что здесь необратимо,
|
||||
говорит `CLAUDE.md` — что в этом проекте необратимо.
|
||||
|
||||
## Блок границ покрытия
|
||||
|
||||
Каждый проход завершает вывод этим блоком. Он не сокращается и не заменяется
|
||||
фразой «всё проверено».
|
||||
|
||||
```
|
||||
## Coverage of this pass
|
||||
- проверено: <что реально прочитано/запущено, с путями и командами>
|
||||
- не проверялось и почему: <бюджет, недоступный инструмент, вне входа>
|
||||
- принципиально недоступно этому проходу: <из charter'а агента>
|
||||
```
|
||||
|
||||
## Финальный отчёт триажа
|
||||
|
||||
Секции строго в этом порядке, потолок — 7 пунктов в первых двух:
|
||||
|
||||
1. `Блокирует мердж` (≤3, каждая с оракулом);
|
||||
2. `Стоит исправить сейчас` (≤4);
|
||||
3. `Гипотезы без доказательства` — что понижено и почему;
|
||||
4. `Promote candidates` — кандидаты в конвенцию или правило линтера;
|
||||
5. `Границы покрытия` — сводная, обязательная.
|
||||
|
||||
Перед секциями — сводка для человека: размер, сложность, метка и режим
|
||||
прогона, состояние гейта, **план разметки задачи с исходом по каждой теме**,
|
||||
сколько находок пришло на вход и сколько осталось.
|
||||
|
||||
**Реестр сводки — темы, а не проходы, и это не оформление.** Перечень запущенных
|
||||
проходов отвечает «все, кто должен был, отработали» и молчит о том, что именно
|
||||
осталось непроверенным: уехавший в старшую метку проход уносит тему с собой
|
||||
беззвучно. План же называет тему, её дом, глубину и исполнителя — и тема,
|
||||
оставшаяся без отчёта, видна сразу. Перечень проходов из сводки не исчезает, но
|
||||
идёт **внутри** плана, колонкой «кто закрывает».
|
||||
|
||||
Каждая находка в секциях 1–2 несёт дополнительное поле:
|
||||
|
||||
```
|
||||
- Действие: инлайн | развилка
|
||||
```
|
||||
|
||||
`инлайн` — оркестратор чинит сам, не спрашивая и не логируя. `развилка` — цена
|
||||
исправления сопоставима с переработкой, либо выбор меняет scope, либо решение
|
||||
трогает инвариант: уезжает вопросом с вариантами и ценой каждого туда, где
|
||||
проект держит вопросы, а работа продолжается на остатке.
|
||||
|
||||
Потребитель отчёта — оркестратор, который **реализует прочитанное**. Поэтому
|
||||
потолок в 7 пунктов — не забота о внимании читателя, а защита кодовой базы от
|
||||
правок, которых никто не заказывал.
|
||||
@@ -0,0 +1,137 @@
|
||||
# Откуда проход берёт проектную конкретику
|
||||
|
||||
Конвейер общий, находки — проектные. Проход, не знающий, что в этом проекте
|
||||
нельзя нарушать, чем краснеет гейт и сколько данных реально идёт через узел,
|
||||
выдаёт правдоподобные общие места: их дорого опровергать и нечем подтверждать.
|
||||
|
||||
Отдельного файла-брифа **нет**. Проектная конкретика живёт в документах канона
|
||||
`av-dev-docs`, и проход читает их напрямую: пути жёсткие, посредник не нужен, а
|
||||
второй дом для тех же фактов разошёлся бы и выглядел актуальным.
|
||||
|
||||
Определение канона — в плагине `av-dev-docs`,
|
||||
`skills/canon/references/canon.md`. Здесь только карта «тема → её дом → что
|
||||
оттуда берётся».
|
||||
|
||||
## Карта тем
|
||||
|
||||
**Дом бывает файлом или каталогом** — `docs/security.md` и `docs/security/`
|
||||
называют одну и ту же тему. Форму дома называет план разметки задачи; проход её не
|
||||
угадывает.
|
||||
|
||||
| Тема | Дом | Что оттуда берётся |
|
||||
| --- | --- | --- |
|
||||
| `requirements` | `openspec/specs/`, `openspec/changes/<id>/specs/` | нормативное поведение и дельты изменения |
|
||||
| `autotests` | `CLAUDE.md`, семантика гейта | команда гейта, чем краснеет безусловно, чего в нём нет, кто гоняет дорогое |
|
||||
| `conventions` | `docs/conventions.*` | конвенции прозой и **что уже механизировано** правилом |
|
||||
| `architecture` | `docs/architecture.*` | компоненты и capability, единые точки проекта |
|
||||
| | источник `docs/passport.*` | что система делает и **чего не делает**, граница домена |
|
||||
| `security` | `docs/security.*` | периметр, недоверенный вход, из чего строятся пути и ключи, что вне модели |
|
||||
| `operations` | `docs/architecture.*`, раздел эксплуатации | окружение, внешние зависимости поимённо, наблюдатель, характер потока |
|
||||
| | источник `docs/database.*` | чем физически лежит запись, что при чтении и записи, настройки с числовым значением |
|
||||
| *тема проекта* | её **свой** документ в `docs/` | то, что проект счёл нужным записать |
|
||||
|
||||
**`docs/adr.*` и `docs/research.*` в этой карте нет намеренно.** Они процессные
|
||||
документы: прогон ревью их не открывает. Раньше первый питал тему `architecture`,
|
||||
второй — `operations` и `requirements`; обе строки убраны, и цена этого названа в
|
||||
`SKILL.md`, раздел «Честный предел».
|
||||
|
||||
**Дом темы зависит ещё и от метки.** На `small` темы `security`, `operations` и
|
||||
`architecture` смотрятся не против домов из этой таблицы, а против **инвариантов
|
||||
`CLAUDE.md`**, и закрывает их `code`. Таблица описывает полный дом темы; сколько
|
||||
из него открыто на этом прогоне, говорит план разметки задачи.
|
||||
|
||||
Сквозное, не привязанное к теме:
|
||||
|
||||
| Что нужно проходу | Где лежит |
|
||||
| --- | --- |
|
||||
| инварианты **с severity рядом с формулировкой** | `CLAUDE.md` (и `AGENTS.md`, если он рядом), раздел инвариантов |
|
||||
| что запускать запрещено, с путями; `testdata`; куда писать временное; имя основной ветки | `CLAUDE.md` |
|
||||
| типовые узлы, типовые ложноположительные, **вопросы по темам**, триггеры метки, недоступно проверке | `docs/review.*`, раздел настройки |
|
||||
| прецеденты: воспроизведённые дефекты с оракулом | `docs/review.*`, журнал |
|
||||
|
||||
**Вопросы проекта привязаны к теме, а не к имени прохода.** Раньше блок в
|
||||
`docs/review.md` адресовался поимённо (`ops: <вопрос>`), и когда проход уехал в
|
||||
старшую метку, вопрос перестал задаваться молча. Тема переезд прохода
|
||||
переживает.
|
||||
|
||||
## Сшивать обязаны проходы
|
||||
|
||||
Раньше эти факты лежали рядом в одном файле, и соседство работало само. Теперь
|
||||
они разложены по домам, и **проход обязан собрать их сам** — иначе снимет верное
|
||||
число и честно понизит находку до гипотезы, потому что сравнить будет не с чем.
|
||||
|
||||
Два обязательных стыка:
|
||||
|
||||
- **замер + настройка.** «Пик 768 МиБ» — аномалия только рядом со строкой
|
||||
«запись лежит сжатой и распаковывается целиком»; «блокировка удерживалась
|
||||
5.019 с» — гарантированный отказ соседа только рядом с известным таймаутом
|
||||
занятости. **Число проход снимает сам, на этом прогоне**, настройки берёт из
|
||||
`docs/database.md`, и сшивают их `ops` и `adversary`. Раньше числа брались из
|
||||
`docs/research/`; теперь этот документ процессный, и замер неизвестной свежести
|
||||
больше не выдаёт себя за оракул.
|
||||
- **инвариант + обратимость.** severity берётся из `CLAUDE.md`; если её там
|
||||
нет — она **выводится по обратимости последствия** и помечается «выведена по
|
||||
обратимости», а не выдаётся за решение проекта.
|
||||
|
||||
**У `basics` стыков нет, и это не упущение.** Он не меряет, поэтому сшивать число
|
||||
с настройкой ему нечего; единственное его основание для `critical` — инвариант из
|
||||
`CLAUDE.md`, всё остальное он формулирует условиями и оставляет гипотезой. Его
|
||||
вход намеренно узкий: дома тем из плана плюс инварианты и журнал. Широкий вход —
|
||||
это метка `large`, и там он есть у `architecture`. Греп по базе ему разрешён
|
||||
точечный — «есть ли второй вызывающий», — но обход всей базы и инвентарь
|
||||
концепций не его работа.
|
||||
|
||||
**У `scope` стыков нет по другой причине: он не читает содержимого.** Его дело —
|
||||
найти дома и раздать темы, а не пересказать написанное. Пересказ сделал бы его
|
||||
посредником между документом и проходом, а посредник расходится с источником и при
|
||||
этом выглядит актуальным.
|
||||
|
||||
## Деградация — поразрядная
|
||||
|
||||
Документа нет — деградирует то, что из него читалось, и **только оно**. Каждый
|
||||
проход пишет **свою** строку в границы покрытия; триаж собирает их в один
|
||||
список и **не сливает в одну строку**: разные пробелы чинятся разным — периметр
|
||||
пишется руками за десять минут, а числа требуют замера.
|
||||
|
||||
**Кто какой документ читает — из документа не выводится, а назначается планом.**
|
||||
Документ питает тему (это записано на стороне канона, таблица «Роли документов и
|
||||
темы ревью»), а тему на этом прогоне закрывает тот, кого назвала разметка задачи; вся
|
||||
раскладка «тема → проход → глубина» — в `SKILL.md` этого скилла и больше нигде.
|
||||
**Списка читателей не ведёт никто, и это не пробел.** Он жил бы на стороне
|
||||
канона, а документ живёт дольше, чем раскладка проходов: список разошёлся бы с
|
||||
конвейером молча и при этом выглядел актуальным. Однажды уже разошёлся.
|
||||
|
||||
Ниже — только **последствие** отсутствия дома, и оно называет самое дорогое, а не
|
||||
всех пострадавших.
|
||||
|
||||
| Нет дома | Что деградирует |
|
||||
| --- | --- |
|
||||
| `CLAUDE.md` без инвариантов | `critical` по основанию «нарушен инвариант проекта» не присваивается никем |
|
||||
| `docs/security.*` | тема `security` остаётся без дома: вопросы задаются по коду, `critical` не ставится, периметр неизвестен |
|
||||
| `docs/database.*` | замер не с чем сравнить: находка темы `operations` не поднимается выше гипотезы |
|
||||
| `docs/passport.*` | тема `architecture` теряет границу домена и вырождается в общее мнение |
|
||||
| `docs/review.*` | `triage` отсеивает вслепую: типовых ложноположительных нет; вопросы проекта по темам не задаются |
|
||||
| `docs/conventions.*` | вторая половина `code` идёт вхолостую: записанных конвенций нет |
|
||||
| `docs/architecture.*` | «не появился ли второй способ» не проверяется — единых точек не знает никто; тема `operations` теряет перечень внешних зависимостей |
|
||||
|
||||
Строка в границах покрытия обязана называть **причину**: «`docs/security.md` в
|
||||
проекте нет» читается иначе, чем «есть, но периметр не назван». Без причины
|
||||
строка неотличима от «мы просто не стали» и перестаёт читаться на третьей задаче.
|
||||
|
||||
**Документов канона нет вовсе** — проект не приведён к канону. Это не повод
|
||||
работать вслепую: скажи об этом строкой и предложи `av-dev-docs:canon`. Одна
|
||||
операция на проект против деградации на каждой задаче.
|
||||
|
||||
## Правило чтения
|
||||
|
||||
- **Читай в источнике, не по памяти.** Документы правятся по ходу работы, в том
|
||||
числе этой же задачей.
|
||||
- **Число без провенанса — условие, а не утверждение.** Число, чей источник по
|
||||
ссылке не подтвердился, читается как условие и **называется расходящимся**, а
|
||||
не подменяется догадкой.
|
||||
- **Пустое, названное пустым, — это факт.** «Внешних зависимостей нет — смотри
|
||||
на диск и на СУБД» экономит обязательный вопрос. Отсутствие строки — не факт,
|
||||
а пробел, и его надо назвать в границах покрытия.
|
||||
- **Свойство, ставшее правилом линтера, из конвенций удалено** и лежит в
|
||||
перечне механизированного в `docs/conventions/README.md`. Проверять его
|
||||
проходом — тратить внимание на уже проверенное.
|
||||
@@ -0,0 +1,120 @@
|
||||
# Промоут: находка → конвенция → правило → удаление
|
||||
|
||||
Механизм храповика. Без него конвейер выдаёт одни и те же находки бесконечно, а
|
||||
конвенции не растут — то есть внимание тратится повторно на уже решённое.
|
||||
|
||||
Роли уровней:
|
||||
|
||||
- **generative-проходы** — механизм *открытия* неявного (дорого, шумно, но
|
||||
только они достают то, чего нет в списках);
|
||||
- **конвенции** — дешёвая *регрессионная сетка* на уже открытое;
|
||||
- **правила линтера** — то же с детерминированным оракулом и нулевой ценой
|
||||
внимания.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
f["находка ревью"]
|
||||
cond{"принята и не специфична<br/>для одного места?"}
|
||||
no["промоуту не подлежит:<br/>место одно — комментарий в коде;<br/>вкусовщина — вон на триаже;<br/>нужен рантайм — в журнал ревью"]
|
||||
conv["конвенция:<br/>проверяемое свойство + какой проход нашёл"]
|
||||
rule["правило линтера, запретитель,<br/>тест-сканер или анализатор"]
|
||||
clean["шаг 3: формулировка удалена из конвенций,<br/>строка — в conventions/README.md"]
|
||||
|
||||
f --> cond
|
||||
cond -->|нет| no
|
||||
cond -->|да| conv
|
||||
conv --> rule
|
||||
rule --> clean
|
||||
rule -->|"ложных чаще, чем ловит (~треть)"| conv
|
||||
```
|
||||
|
||||
Ребро назад — обратное движение (внизу): правило, дающее ложные срабатывания
|
||||
чаще, чем ловит, снимается в прозу. Ребро `rule → clean` **обязательное**: без
|
||||
него первые два шага не окупаются, а именно его и пропускают.
|
||||
|
||||
Схема — **сводка**: условия каждого шага в его разделе, и при расхождении прав
|
||||
текст.
|
||||
|
||||
## Шаг 1. Находка → конвенция
|
||||
|
||||
Условия: находка **принята** при ревью (не отвергнута, не понижена в гипотезу) и
|
||||
**не специфична для одного места**.
|
||||
|
||||
- Формулируется как **проверяемое свойство**, а не как совет: «уровень доменного
|
||||
отказа выбирает единственный логирующий чекпоинт», а не «внимательнее с
|
||||
уровнями логов».
|
||||
- Записывается источник — какой проход нашёл. Это единственные данные для
|
||||
калибровки: проход, чьи находки регулярно доезжают до конвенции, оправдан;
|
||||
проход, чьи находки не доезжают никогда, — кандидат на `drop` (см.
|
||||
[calibration.md](calibration.md)).
|
||||
- Место записи — конвенции проекта, файл или нужный файл каталога (путь — в
|
||||
каталог `docs/conventions/`). Если
|
||||
тема относится к поведению системы, а не к тому, как мы пишем код, — это не
|
||||
конвенция, а требование: заводится дельта-спека обычным путём.
|
||||
|
||||
Промоут идёт **тем же путём, что change → spec**: правка попадает в тот же
|
||||
коммит, что и исправление кода, с пометкой в сообщении — история промоутов
|
||||
остаётся видна в `git log` по файлу конвенций.
|
||||
|
||||
## Шаг 2. Конвенция → правило
|
||||
|
||||
Как только свойство выражается детерминированно, оно переезжает в инструмент.
|
||||
Порядок предпочтения — от дешёвого к дорогому:
|
||||
|
||||
1. **готовое правило существующего линтера** — включить в конфиг;
|
||||
2. **запрет идентификатора или импорта** правилом-«запретителем» с собственным
|
||||
паттерном;
|
||||
3. **правило с настройкой формы** — когда важно не имя, а конструкция;
|
||||
4. **тест-сканер исходников** — когда правило про структуру проекта или про
|
||||
схему: направление зависимостей, форма миграций, матчинг ошибки по тексту,
|
||||
бизнес-логика в транспорте;
|
||||
5. **собственный анализатор** — последний рубеж, заводим только если 1–4 не
|
||||
выражают правило.
|
||||
|
||||
Правило обязано быть **зелёным на текущем коде в момент включения**: иначе
|
||||
хук блокирует любой коммит, и правило снимут первым же раздражённым движением.
|
||||
Приводить код в соответствие — часть шага 2, отдельным коммитом.
|
||||
|
||||
## Шаг 3. Удаление из конвенций и из промптов
|
||||
|
||||
**Шаг, который пропускают чаще всего, и единственный, ради которого затевались
|
||||
первые два.**
|
||||
|
||||
Как только правило работает:
|
||||
|
||||
- из файла конвенций убирается формулировка правила; остаётся, если нужно, одна
|
||||
строка «проверяется линтером `<имя>`» — но только там, где без неё раздел
|
||||
теряет связность;
|
||||
- правило переезжает в **перечень механизированного в
|
||||
`docs/conventions/README.md`** — со ссылкой на место механизации: конфиг
|
||||
линтера, собственный анализатор, тест-сканер исходников. Не названное место
|
||||
означает, что проход будет добросовестно проверять уже проверенное;
|
||||
- из контекста инструмента спек убирается дубль, если он там был.
|
||||
|
||||
Charter'ы проходов при этом **не правятся**: они общие и живут в плагине, а
|
||||
предмет проверки приходит из документов проекта. Именно поэтому шаг 3 дешевле,
|
||||
чем был:
|
||||
вычеркнуть строку в одном файле проекта, а не в девяти промптах.
|
||||
|
||||
Практический критерий: **в прозаических конвенциях остаётся только то, что
|
||||
принципиально не выражается правилом.** Файл конвенций на несколько сотен строк
|
||||
размазывает внимание модели по тривиальному — она добросовестно проверит
|
||||
именование полей лога и не дойдёт до формы решения. Каждая строка конвенций,
|
||||
которую можно было бы проверить машиной, оплачивается дефектом, который не
|
||||
поймали где-то ещё.
|
||||
|
||||
## Обратное движение
|
||||
|
||||
Правило, которое даёт ложные срабатывания чаще, чем ловит (порядка трети от
|
||||
общего числа), снимается и возвращается в прозу — или удаляется совсем, если
|
||||
свойство перестало быть важным. Снятие фиксируется там же, где включалось, с
|
||||
одной строкой «почему».
|
||||
|
||||
## Что промоуту не подлежит
|
||||
|
||||
- Находка, специфичная для одного места (её лечит комментарий в коде).
|
||||
- Вкусовщина: не меняет поведения, не влияет на стоимость следующего изменения,
|
||||
не нарушает записанного. Такое выбрасывается на триаже и не хранится.
|
||||
- Свойство, требующее знания рантайма (профиль нагрузки, история инцидентов) —
|
||||
его нельзя проверить ни промптом, ни линтером; место такому — в журнале ревью
|
||||
как «признано неавтоматизируемым» (см. [review-journal.md](review-journal.md)).
|
||||
@@ -0,0 +1,106 @@
|
||||
# Журнал дефектов
|
||||
|
||||
Артефакт проекта, а не плагина: файл живёт в репозитории — **`docs/review.md`**,
|
||||
слот канона `av-dev-docs`. Здесь описано, зачем он и какой формы, потому что без
|
||||
него конвейер не учится: находки закрываются, а почему их не поймали — забывается,
|
||||
и один и тот же класс проскакивает второй раз.
|
||||
|
||||
Тот же файл держит **настройку конвейера под проект** — типовые узлы, типовые
|
||||
ложноположительные, вопросы по темам, недоступно проверке. Это не соседство по
|
||||
случаю: все четыре раздела — производные калибровки, а журнал им источник.
|
||||
|
||||
## Что туда попадает
|
||||
|
||||
**Воспроизведённый дефект — с пометкой `проскочил` или `пойман ревью`.**
|
||||
Записывается **сразу**, а не ретроспективно: со временем теряется не сам факт, а то,
|
||||
почему дефект не поймали, — единственное, ради чего журнал существует.
|
||||
|
||||
Пометка делит журнал на две выборки с разным назначением:
|
||||
|
||||
- **проскочил** — проверочный набор для калибровки конвейера. Реальный промах сильнее
|
||||
синтетической пробы: синтетические смещены в сторону тех, которые уже умеешь
|
||||
придумывать;
|
||||
- **пойман ревью** — прецеденты с оракулом. Самая сильная опора, какая у прохода
|
||||
бывает: проектная, воспроизводимая и однажды уже оказавшаяся правдой. Без
|
||||
журнала они остаются только в отчётах триажа в архиве change, где их никто не
|
||||
ищет.
|
||||
|
||||
Реализованные задачи и принятые решения сюда не пишутся: у них есть коммит, спека
|
||||
и `docs/adr/`.
|
||||
|
||||
Отдельно сюда попадают **решения о составе прогонов**: перестали звать проход,
|
||||
понизили метку правилом, сузили класс проверяемого. Не потому, что это промах,
|
||||
а потому, что здесь лежит цена: если что-то теперь проскочит, первый вопрос —
|
||||
«не тот ли это класс, который мы перестали проверять».
|
||||
|
||||
Каждое такое решение обязано получить строку в подразделе **«Перестали проверять
|
||||
сознательно»** раздела «Недоступно проверке» того же файла. Журнал хранит «почему
|
||||
тогда так решили», раздел настройки — то, во что смотрит каждый прогон. Решение,
|
||||
оставшееся только в журнале, в границы покрытия не доедет.
|
||||
|
||||
## Форма записи
|
||||
|
||||
**Это дом формы, и у него есть копия.** Скелет `docs/review.md`, который кладёт
|
||||
в проект `av-dev-docs` (`skills/canon/references/skeletons.md`), повторяет её
|
||||
дословно — он уезжает в репозиторий и обязан там что-то говорить. Правка формы
|
||||
здесь **обязана** тянуть правку скелета и запись в журнал версий канона; иначе
|
||||
проекты продолжат писать по старой форме, а конвейер — ждать поля, которого нет.
|
||||
Дословность сверяет `scripts/copies.py` маркетплейса по маркерам ниже — но
|
||||
запись в журнал версий он не проверит, это остаётся на человеке.
|
||||
|
||||
<!-- дом: журнал-дефектов-форма -->
|
||||
```
|
||||
## ГГГГ-ММ-ДД — <краткое последствие> [проскочил|пойман]
|
||||
|
||||
- **Где:** путь:строка либо «конвейер, а не код»
|
||||
- **Симптом:** как обнаружилось, кем и когда
|
||||
- **Причина:** что на самом деле было не так
|
||||
- **Чем воспроизведён:** тест, команда, замер — с числами
|
||||
- **Почему не поймали:** только для проскочивших — какой проход обязан был найти
|
||||
и что ему помешало
|
||||
- **Что меняем:** правило прохода, шаг гейта, конвенция, факт в документе
|
||||
проекта — либо «ничего, цена поимки выше цены дефекта»
|
||||
```
|
||||
<!-- /дом: журнал-дефектов-форма -->
|
||||
|
||||
Пункт «чем воспроизведён» отличает запись от байки: без него на неё нельзя
|
||||
сослаться как на оракул. Регрессионный тест, написанный вместе с починкой,
|
||||
годится наравне с независимым экспериментом — он исполняемый и падает на старом
|
||||
коде. Слабее он ровно в одном: сформулирован уже зная ответ, и это отмечается
|
||||
словом.
|
||||
|
||||
Последний пункт важнее остальных. Вывод «ничего не меняем» — законный исход: не
|
||||
всякий дефект стоит того, чтобы усложнять ради него ревью каждой задачи.
|
||||
|
||||
## Куда ведёт запись
|
||||
|
||||
Три адреса, и выбор между ними — половина ценности журнала:
|
||||
|
||||
- **в документ проекта** — если проход не мог знать факта. Адрес зависит от рода
|
||||
факта, и карта их всех — [project-facts.md](project-facts.md):
|
||||
настройка хранилища → `docs/database.md`;
|
||||
что необратимо и какой шаг гейта красит безусловно → `CLAUDE.md`; периметр и
|
||||
недоверенный вход → `docs/security.*`. **Вопрос по теме**, если промах лечится
|
||||
не фактом, а заданным вопросом, → раздел «Вопросы по темам» того же
|
||||
`docs/review.*`; адресуй теме, а не имени прохода — проход уедет между
|
||||
метками, тема останется. Самый частый адрес и самый дешёвый. Прежде чем
|
||||
править charter, проверь, не хватит ли факта или вопроса: charter общий для
|
||||
всех проектов, документ — про этот.
|
||||
- **в конвенции или в правило линтера** — если свойство выражается
|
||||
детерминированно (процедура — [promote.md](promote.md)).
|
||||
- **в charter прохода** — если сломан **метод**, а не знание. Правка charter'а
|
||||
меняет поведение во всех проектах, поэтому она требует калибровки
|
||||
([calibration.md](calibration.md)) и обоснования, почему это не лечится фактом
|
||||
в документе проекта.
|
||||
|
||||
## Что журнал даёт конвейеру
|
||||
|
||||
- **пробы для калибровки** — выборка по пометке `проскочил`;
|
||||
- **готовые оракулы** — выборка по пометке `пойман ревью`: находка того же
|
||||
класса подтверждается ссылкой на запись, а не рассуждением;
|
||||
- **основание для правил конвейера** — требование называть запущенные проходы
|
||||
поимённо, отказ от чисел, производных от размера корпуса, и правило очереди для
|
||||
меряющих проходов выведены из конкретных записей, а не из общих соображений;
|
||||
- **счётчик обратимости решений** — сузили состав проходов и через месяц поймали
|
||||
дефект ровно того класса, который перестали проверять: решение пересматривается
|
||||
фактом, а не спором.
|
||||
@@ -0,0 +1,165 @@
|
||||
# Метки задачи — выбор, цена, доли
|
||||
|
||||
**Дом правила выбора метки.** Состав проходов по каждой метке, схема процесса и
|
||||
раздача тем живут в [SKILL.md](../SKILL.md) — там диспетчер, и на готовой задаче
|
||||
его достаточно. Здесь то, что читают, когда метку **выбирают, оспаривают или
|
||||
калибруют**.
|
||||
|
||||
Применяет правило `review-scope` при разметке задачи — не автор изменения. Его
|
||||
рабочая выжимка лежит в уставе агента; расходиться она с этим файлом не вправе, а
|
||||
при расхождении прав этот.
|
||||
|
||||
## Правило выбора — две оси, а не один вопрос
|
||||
|
||||
**Оси две, они измеряют разное, и метка есть максимум по ним.**
|
||||
|
||||
| | **знакомое** — форму решения можно назвать до начала | **незнакомое** — форму предстоит нащупать по ходу |
|
||||
|---|---|---|
|
||||
| **малое** — один узел | `small` | `large` |
|
||||
| **среднее** — несколько узлов одного слоя | `medium` | `large` |
|
||||
| **крупное** — несколько слоёв, перенос ответственности, большой рефакторинг | `large` | `large` |
|
||||
|
||||
**Метка — не синоним размера.** Совпадают они только в левом верхнем углу: малое
|
||||
**незнакомое** изменение получает `large`, трогая один узел. Поэтому в плане
|
||||
стоят три строки, а не одна: размер, сложность и метка — каждая со своим
|
||||
обоснованием. Проход, выведший объём диффа из метки, ошибётся ровно на этом
|
||||
случае — а он и есть самый опасный: незнакомая форма в одном узле течёт там, где
|
||||
её никто не ждёт.
|
||||
|
||||
**Размер** — про объём: сколько мест трогается. **Сложность** — про
|
||||
неизвестность: знаем ли мы форму решения заранее. Признак незнакомого простой и
|
||||
проверяемый: **перед работой нельзя назвать, какие узлы будут тронуты**.
|
||||
|
||||
Раньше обе оси были склеены в один вопрос «крупное **или** незнакомое?». Ответ
|
||||
получался тот же, но две вещи под одним именем не измеришь по отдельности, и
|
||||
потому разметка не могла сказать «изменение среднее, но совершенно знакомое» —
|
||||
а именно эта пара и есть рабочее умолчание. Теперь обе оси называются в плане
|
||||
поимённо, и обе — с обоснованием.
|
||||
|
||||
**Оси называются и на стадии дизайна, и на стадии кода — но считаются один
|
||||
раз.** Это и есть причина, по которой разметка переехала к `propose`: состав
|
||||
ревью дизайна выводится из той же пары, что и состав ревью кода, а считать её
|
||||
дважды значит один раз посчитать без разведённости с автором.
|
||||
|
||||
**Обратимость — не третья ось, а отрицательный тест.** Она не уточняет размер и
|
||||
не уточняет сложность: она запрещает нижнюю метку независимо от обеих.
|
||||
|
||||
**Отрицательный тест `small`, и он важнее положительного:** изменение, которое
|
||||
после мерджа **не откатывается обратной правкой**, — не `small`, каким бы
|
||||
маленьким ни был дифф. Сюда попадают миграция схемы и данных, формат на диске,
|
||||
публичный контракт, имя, которое разойдётся по кодовой базе. Три строки миграции
|
||||
— это `medium`, а не `small`: размер диффа и цена ошибки здесь расходятся.
|
||||
|
||||
Что здесь считается крупным, что — незнакомым и что — мелким, проект уточняет в
|
||||
`docs/review.md`, подразделе «Триггеры метки»: **тремя списками** — по одному на
|
||||
каждую ось вверх и один вниз, поимённо, узлами или capability. Это **уточнение**,
|
||||
а не отмена: не записано — работает таблица выше.
|
||||
|
||||
## Спорный случай решается вниз, и у этого есть цена
|
||||
|
||||
Правило асимметрично, потому что асимметрична цена ошибки.
|
||||
|
||||
- **Спорно между `medium` и `large` → бери `medium`.** Ошибка в эту сторону
|
||||
стоит находки, которая всплывёт на следующей задаче или в журнале дефектов.
|
||||
Ошибка в обратную стоит трёх тяжёлых проходов, двое из которых держат машину и
|
||||
идут цепочкой, — и платится она **на каждой** задаче, выбранной неверно.
|
||||
- **Спорно между `small` и `medium` → бери `medium`.** Раньше эта строка
|
||||
обосновывалась тем, что состав одинаков и ошибка почти бесплатна. Теперь состав
|
||||
разный, и обоснование стало прямо противоположным: на `small` три темы ядра
|
||||
смотрятся **только против записанных инвариантов**, а спорный случай — ровно тот,
|
||||
где неизвестно, покрыт ли он инвариантом. Сомнение здесь стоит дороже, чем
|
||||
раньше, и потому решается вниз тем более твёрдо.
|
||||
|
||||
**Выбор сделан в пользу пропускной способности, и это записано, а не подразумевается.**
|
||||
Конвейер настроен на поток задач, а не на максимум находок с каждой: поправить в
|
||||
следующей задаче дешевле, чем держать одну два часа. Отсюда три обязанности,
|
||||
без которых сделка превращается в незаметную потерю качества:
|
||||
|
||||
- **границы покрытия называют темы и их глубину**, а не только запущенные
|
||||
проходы — иначе `small` выглядит так же, как `large` без находок;
|
||||
- **журнал дефектов в `docs/review.md` перестаёт быть хорошей практикой и
|
||||
становится единственной обратной связью**: проскочивший дефект — единственный
|
||||
сигнал, что метка выбрана слишком низко;
|
||||
- **возврат в код — повод пересмотреть метку.** Задача, которая приходит в тот
|
||||
же узел третий раз, уже не мелкая, чем бы ни выглядел её дифф.
|
||||
|
||||
## Метка — максимум по поверхности
|
||||
|
||||
**Обе оси меряются по всему диффу разом, и максимум по каждой отвечает за весь
|
||||
дифф.** Метка изменения — не средневзвешенное: одна строка в перечне границ
|
||||
задачи поднимает метку всему остальному, включая ту часть, которая сама по себе
|
||||
была бы `small`.
|
||||
|
||||
Обратное тоже верно и тоже не бесплатно: у каждой задачи есть **несокращаемый
|
||||
костяк — гейт, спеки, код, триаж**. Разрезать задачу, обе половины которой
|
||||
остаются в одной метке, значит заплатить костяк дважды за ту же проверку.
|
||||
Резать стоит там, где разрез **снимает доказательство с большей части диффа**.
|
||||
Шов и правило нарезки живут у того, кто ведёт задачи, — скилл `av-dev-tasks:tasks`,
|
||||
его `references/split.md`. Пути туда конвейер не выносит: за пределы своего
|
||||
плагина он ходит вызовом скилла, а не файлом.
|
||||
|
||||
Разметка в костяк не входит — она платится один раз на задачу, а не один раз на
|
||||
прогон, и потому **разрез задачи её не удваивает**. Это единственное, что стало
|
||||
дешевле от переезда разметки к `propose`, и это же снимает прежний довод против
|
||||
нарезки.
|
||||
|
||||
**Размер, сложность, метка и глубина объявляются в отчёте, и все четыре с
|
||||
обоснованием.** Метка выбирает `review-scope`; он вправе и поднять, и понизить
|
||||
её — но не молча: строка «метка X, потому что размер Y и сложность Z»
|
||||
обязательна на каждом прогоне, а не только когда метка отличается от ожидаемой.
|
||||
|
||||
## Чем `small` дешевле `medium` и что это стоит
|
||||
|
||||
Экономят три рычага — непуск, вход, потолок, — и они общие для всех проходов и
|
||||
всех меток; их дом и точные числа в [SKILL.md](../SKILL.md), раздел «Модель по
|
||||
проходу». Здесь только то, что рычаги делают **с этой меткой**:
|
||||
|
||||
1. **Составом.** `basics` на `small` не запускается — кроме случая, когда у
|
||||
проекта есть свои темы; тогда он идёт **только с ними**, ровно как в `large`.
|
||||
Три темы ядра, которые он держал бы, переходят к `code` сверкой по
|
||||
инвариантам.
|
||||
2. **Входом.** На `small` `specs` читает только дельта-спеку, а `code` — только
|
||||
**индекс** конвенций (перечень родов и что механизировано), не весь их дом. На
|
||||
`medium` оба читают дома целиком.
|
||||
3. **Потолком.** На `small` потолки самые жёсткие из трёх меток, и каждый
|
||||
напечатан в границах покрытия своего прохода.
|
||||
|
||||
**Что `small` за это не проверяет, названо поимённо и обязано идти строкой в
|
||||
границы покрытия:** темы `security`, `operations` и `architecture` смотрятся
|
||||
только против **записанных инвариантов** `CLAUDE.md`. Свойство, которого в
|
||||
инвариантах нет, с этой меткой не спросит никто — ни сценарием, ни чтением
|
||||
дома темы. Это и есть цена метки, и она заметно больше прежней: раньше `small`
|
||||
отличался от `medium` одним проходом на один вопрос, то есть не экономил
|
||||
ничего и назывался отдельной меткой зря.
|
||||
|
||||
**`large` назван по тому, что он добавляет: вход шире диффа.** Он единственный, где
|
||||
живут тяжёлые проходы, и единственный, где что-то **запускается**. `basics` в нём
|
||||
берёт только проектные темы; своих тем у проекта нет — он не запускается вовсе, и
|
||||
план говорит об этом строкой. **На `small` действует то же правило и по той же
|
||||
причине** — приёмник запускается только тогда, когда ему есть что принимать.
|
||||
Совпадение неслучайное: `basics` держит темы ядра ровно при одной метке из трёх,
|
||||
а приёмником проектных тем работает на всех.
|
||||
|
||||
## Доли — не пожелание, а проверка правила, и проверок две
|
||||
|
||||
**Сверху: `large` — 5–10%.** Если туда уходит каждая третья задача, метку
|
||||
выбирают по ощущению важности. Обратный перекос виден по журналу проскочивших
|
||||
дефектов: класс, который ловят только меряющие проходы, начинает всплывать после
|
||||
мерджа.
|
||||
|
||||
**Снизу: `small` не должен обгонять `medium`.** Ориентир — до трети задач, но
|
||||
сравнение важнее числа: **перевес `small` над `medium` значит, что рабочее
|
||||
умолчание сместилось, а решения об этом никто не принимал.** Проверка нужна
|
||||
именно теперь: пока две нижние метки совпадали составом, дрейф между ними не
|
||||
стоил ничего, и проверки не было. Сейчас он стоит трёх тем ядра, которые на
|
||||
`small` смотрятся только против инвариантов, — то есть ровно того, чем `small` и
|
||||
дёшев.
|
||||
|
||||
Считается это по журналу дефектов и по отчётам, а не по ощущению: метка
|
||||
напечатана в каждом отчёте, и посчитать её за спринт — работа на минуту.
|
||||
|
||||
**У дрейфа вниз есть свой стимул, и его стоит назвать.** `small` дешевле по
|
||||
времени и по деньгам, а выбирает метку хоть и не автор, но проход, читающий
|
||||
описание, написанное автором. Занижённое описание даёт занижённую метку без
|
||||
чьего-либо злого умысла — потому корректор и вынесен в `code`, который смотрит
|
||||
уже на код, а не на описание.
|
||||
Reference in New Issue
Block a user