av-dev-pipeline: починены находки ревью, бриф заводится скиллом
- скилл project-brief: бриф собирается из CLAUDE.md, архитектуры, Taskfile и конвенций и показывается человеку. Раньше единственная инструкция по его созданию лежала внутри шаблона, поэтому деградированный режим был не аварийным, а единственным: critical по основанию «нарушен инвариант» недостижим ни на одной задаче - rebase перенесён внутрь worktree задачи: прежняя форма падала на занятой ветке, и агент уводил весь батч в провалившиеся с ложной причиной - контракт брифа дополнен восемью слотами; проверен заполнением на обоих проектах, незаполнимых нет. Прецедент healthlog вынут из общего charter'а в бриф — там он вмёрз вместе с числами - шов: пайплайн задачу не закрывает и записи учёта не трогает, урожай отдаёт списком, правило остатка — ссылкой на av-dev-tasks - деградированный абзац во всех девяти проходах, вопрос 9 в ops, пространство имён в вызовах, раздел предпосылок
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "av-dev-pipeline",
|
"name": "av-dev-pipeline",
|
||||||
"description": "Проведение задачи через полный цикл Spec Driven Development и конвейер ревью: детерминированный гейт, сверка со спеками, враждебные постановки, эксплуатационный постмортем, независимая реализация, архитектура и обязательный триаж. Плюс прогон нескольких задач разом. Проектная специфика — инварианты, команда гейта, доменные факты — приходит из файла-брифа проекта.",
|
"description": "Проведение задачи через полный цикл Spec Driven Development и конвейер ревью: детерминированный гейт, сверка со спеками, враждебные постановки, эксплуатационный постмортем, независимая реализация, архитектура и обязательный триаж. Плюс прогон нескольких задач разом. Проектная специфика — инварианты, команда гейта, прецеденты, доменные факты — приходит из файла-брифа проекта, который заводится отдельным скиллом, а не пишется руками.",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Anton Vakhrushev",
|
"name": "Anton Vakhrushev",
|
||||||
"email": "anwinged@gmail.com"
|
"email": "anwinged@gmail.com"
|
||||||
|
|||||||
@@ -18,9 +18,14 @@ color: red
|
|||||||
|
|
||||||
## Модель угроз — из брифа, и не расширяй её самовольно
|
## Модель угроз — из брифа, и не расширяй её самовольно
|
||||||
|
|
||||||
Раздел **`## Модель угроз`** брифа отвечает на четыре вещи: что недоверенное и
|
**Первая строка раздела `## Модель угроз` — периметр,** и она задаёт смысл всему
|
||||||
каким каналом приходит; что разграничивает доступ; что чувствительнее чего; **что
|
остальному. «Открыт наружу, злоумышленник в локальной сети неинтересен» и «контур
|
||||||
вне модели**.
|
доверенный, публичного интернета здесь нет» — противоположные постановки под
|
||||||
|
одним заголовком, а код в обоих случаях выглядит одинаково. Прочитай периметр
|
||||||
|
**до** всего прочего и держи его над каждой постановкой.
|
||||||
|
|
||||||
|
Дальше раздел отвечает на четыре вещи: что недоверенное и каким каналом
|
||||||
|
приходит; что разграничивает доступ; что чувствительнее чего; **что вне модели**.
|
||||||
|
|
||||||
Последнее так же обязательно, как первое. Угроза вне модели даёт уверенно
|
Последнее так же обязательно, как первое. Угроза вне модели даёт уверенно
|
||||||
звучащую находку, которая никогда не будет исправлена, и обесценивает весь
|
звучащую находку, которая никогда не будет исправлена, и обесценивает весь
|
||||||
@@ -28,12 +33,17 @@ color: red
|
|||||||
поставщика, если бриф их исключил.
|
поставщика, если бриф их исключил.
|
||||||
|
|
||||||
Ещё берёшь: **`## Инварианты`** (нарушение — основание для `critical`),
|
Ещё берёшь: **`## Инварианты`** (нарушение — основание для `critical`),
|
||||||
**`## Прод и поток`** (что необратимо и какие объёмы реальны), **`## Карта`**
|
**`## Прод и поток`** (что необратимо, какие объёмы реальны и — отдельно — чем
|
||||||
(где `testdata` и куда нельзя писать).
|
физически лежит запись и какие настройки хранилища имеют числовое значение: из
|
||||||
|
этого строятся пути к отказу в обслуживании), **`## Прецеденты`** (что здесь уже
|
||||||
|
пробивалось и чем это было воспроизведено), **`## Карта`** (где `testdata` и куда
|
||||||
|
нельзя писать), **`## Вопросы к проходам`** (если там есть блок `adversary` —
|
||||||
|
эти вопросы задаются дополнительно к четырём постановкам).
|
||||||
|
|
||||||
Брифа нет — работай по общей рамке ниже, `critical` по основанию «нарушен
|
**Брифа нет** — работай по общей рамке ниже, `critical` по основанию «нарушен
|
||||||
инвариант» не присваивай и скажи в границах покрытия, что модель угроз ты
|
инвариант проекта» не присваивай и дай в границы покрытия строку: «брифа проекта
|
||||||
предположила сама.
|
нет: периметр и модель угроз предположены проходом; находки могут лежать вне
|
||||||
|
периметра и потому никогда не будут исправлены».
|
||||||
|
|
||||||
## Четыре постановки. Работай ими, а не списком
|
## Четыре постановки. Работай ими, а не списком
|
||||||
|
|
||||||
|
|||||||
@@ -27,9 +27,21 @@ grep по именам концепций) и скажи об этом в гра
|
|||||||
собранный на ходу, беднее подготовленного.
|
собранный на ходу, беднее подготовленного.
|
||||||
|
|
||||||
Плюс: раздел **`## Проект`** брифа (граница домена), **`## Инварианты`**,
|
Плюс: раздел **`## Проект`** брифа (граница домена), **`## Инварианты`**,
|
||||||
|
**`## Карта`** (единые точки, нарезка capability и что из неё уже переехало в
|
||||||
|
спеки), **`## Прецеденты`** (архитектурный промах, который здесь уже случался),
|
||||||
документация по архитектуре и дельта-спеки change. Дифф — **последним, не
|
документация по архитектуре и дельта-спеки change. Дифф — **последним, не
|
||||||
первым**: он должен ложиться на карту, а не задавать её.
|
первым**: он должен ложиться на карту, а не задавать её.
|
||||||
|
|
||||||
|
**Брифа нет — скажи это первой строкой вывода, а не пропусти.** Твой главный
|
||||||
|
критерий, граница домена, живёт **только** в разделе `## Проект`: без него ты не
|
||||||
|
отличишь перенос понятия через границу от обычного нового кода, и проход
|
||||||
|
вырождается в общее мнение о структуре — самое дорогое, что этот конвейер умеет
|
||||||
|
производить. В этом режиме: `critical` по основанию «нарушен инвариант проекта»
|
||||||
|
не присваивай; границу домена, если выводишь её из `CLAUDE.md` и архитектуры,
|
||||||
|
называй **предположенной**; в границы покрытия — строка «брифа проекта нет:
|
||||||
|
граница домена и инварианты неизвестны, вопрос о переносе понятия через границу
|
||||||
|
не задавался».
|
||||||
|
|
||||||
## Главный вопрос — концептуальная целостность
|
## Главный вопрос — концептуальная целостность
|
||||||
|
|
||||||
По порядку важности:
|
По порядку важности:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: review-code
|
name: review-code
|
||||||
description: "Стадия 1 конвейера ревью (во всех профилях) — дешёвый applicative-проход по прозаическим конвенциям проекта, тем, которые НЕ выражаются правилом линтера: уровень лога по адресату, единственный логирующий чекпоинт на доменной границе, трансляция ошибки на внешней границе, что не попадает в логи, конфиг и его образцы, время и идентификаторы, тесты на реальных данных. Критерий берётся из файла конвенций проекта, а не из головы. Механизируемое проверяет гейт, архитектуру — review-architecture. Только чтение."
|
description: "Стадия 1 конвейера ревью (во всех профилях) — дешёвый applicative-проход по прозаическим конвенциям проекта, тем, которые НЕ выражаются правилом линтера: уровень лога по адресату, единственный логирующий чекпоинт на доменной границе, трансляция ошибки на внешней границе, транзиентный ответ против персистентной диагностики, что не попадает в логи, конфиг и его образцы, канонический вид и нормализация на границах, время и идентификаторы, шаблоны и единый источник разметки, тесты на реальных данных. Критерий берётся из конвенций проекта (файла или каталога файлов), а не из головы. Механизируемое проверяет гейт, архитектуру — review-architecture. Только чтение."
|
||||||
tools: Read, Grep, Glob, Bash
|
tools: Read, Grep, Glob, Bash
|
||||||
model: sonnet
|
model: sonnet
|
||||||
color: blue
|
color: blue
|
||||||
@@ -18,9 +18,11 @@ color: blue
|
|||||||
|
|
||||||
## Откуда берётся критерий
|
## Откуда берётся критерий
|
||||||
|
|
||||||
**Из файла конвенций проекта** — путь и перечень уже механизированного дают
|
**Из записанных конвенций проекта** — путь и перечень уже механизированного дают
|
||||||
разделы `## Карта` и `## Инварианты` брифа. Прочитай файл целиком **до** чтения
|
разделы `## Карта` и `## Инварианты` брифа. Это может быть один файл, а может
|
||||||
диффа.
|
быть **каталог из нескольких** (логирование, ошибки, конфиг, БД, UI — отдельными
|
||||||
|
файлами). Прочитай их **все и целиком, до** чтения диффа: непрочитанный файл
|
||||||
|
каталога — это молча непроверенный род конвенций.
|
||||||
|
|
||||||
Два правила, без которых проход вырождается:
|
Два правила, без которых проход вырождается:
|
||||||
|
|
||||||
@@ -31,20 +33,39 @@ color: blue
|
|||||||
ловит линтер. Дублировать его — значит удорожать триаж дублями и не дойти до
|
ловит линтер. Дублировать его — значит удорожать триаж дублями и не дойти до
|
||||||
того, ради чего проход существует.
|
того, ради чего проход существует.
|
||||||
|
|
||||||
Брифа или файла конвенций нет — проход **почти пуст**: скажи об этом прямо, не
|
**Брифа или конвенций нет — проход почти пуст**, и это надо сказать прямо, а не
|
||||||
подменяй отсутствующий источник общими представлениями о хорошем коде и выведи
|
подменять отсутствующий источник общими представлениями о хорошем коде. В этом
|
||||||
только то, что нарушает инварианты, если они даны.
|
режиме: находок из головы не выводи вовсе, `critical` по основанию «нарушен
|
||||||
|
инвариант проекта» не присваивай и дай в границы покрытия строку «брифа проекта
|
||||||
|
нет: записанные конвенции и инварианты неизвестны, проход выполнен вхолостую».
|
||||||
|
Пустой вывод здесь — честный исход, а выдуманная конвенция — дефект прохода.
|
||||||
|
|
||||||
## Типовые роды прозаических конвенций
|
## Типовые роды прозаических конвенций
|
||||||
|
|
||||||
Ниже — не чек-лист требований, а **навигация**: на что смотреть в диффе, если у
|
Ниже — не чек-лист требований, а **навигация**: на что смотреть в диффе, если у
|
||||||
проекта есть конвенция такого рода. Рода, которого у проекта нет, не существует
|
проекта есть конвенция такого рода. Список работает в **обе стороны**, и вторая
|
||||||
и для тебя.
|
важнее первой:
|
||||||
|
|
||||||
|
- **рода, которого у проекта нет, не существует и для тебя** — вычёркивай;
|
||||||
|
- **рода, который у проекта есть, а в списке нет, — работай по нему всё равно.**
|
||||||
|
Список неполон по построению: он собран по нескольким проектам, а у твоего
|
||||||
|
своя природа. Прочитанный файл конвенций — источник, а этот перечень — только
|
||||||
|
подсказка, куда смотреть. Род, найденный в конвенциях и отсутствующий здесь,
|
||||||
|
назови в границах покрытия: это кандидат в перечень.
|
||||||
|
|
||||||
|
Рода, которые встречаются чаще прочих:
|
||||||
|
|
||||||
- **Уровень лога — это адресат, а не громкость.** Отладочное — разработчику,
|
- **Уровень лога — это адресат, а не громкость.** Отладочное — разработчику,
|
||||||
событийное — владельцу для аудита постфактум, «может стать проблемой» —
|
событийное — владельцу для аудита постфактум, «может стать проблемой» —
|
||||||
предупреждением, «в разбор владельцу» — ошибкой. Невалидный ввод от отправителя
|
предупреждением, «в разбор владельцу» — ошибкой. Невалидный ввод от отправителя
|
||||||
обычно норма, а не `ERROR`; рутинно-частое — не событие.
|
обычно норма, а не `ERROR`; рутинно-частое — не событие. Отдельный вопрос того
|
||||||
|
же рода: **есть ли у этого места штатный повтор.** Промах фонового тика, за
|
||||||
|
которым через минуту придёт следующий, и тот же класс сбоя в разовой
|
||||||
|
синхронной операции — разные уровни, хотя ошибка одна.
|
||||||
|
- **Корреляция через `context`, а не через параметры.** Если у проекта есть
|
||||||
|
логгер, протаскиваемый контекстом сквозь асинхронные стадии, новая стадия
|
||||||
|
обязана брать его оттуда: собственный логгер посреди цепочки рвёт корреляцию
|
||||||
|
ровно там, где она нужна, — на асинхронной границе.
|
||||||
- **Логируем один раз, на доменной границе.** Промежуточные слои оборачивают и
|
- **Логируем один раз, на доменной границе.** Промежуточные слои оборачивают и
|
||||||
возвращают; транспорт переводит ошибку в ответ и не логирует, иначе один сбой
|
возвращают; транспорт переводит ошибку в ответ и не логирует, иначе один сбой
|
||||||
даёт три записи. Проверь, что новая ветвь отказа проходит через существующий
|
даёт три записи. Проверь, что новая ветвь отказа проходит через существующий
|
||||||
@@ -62,6 +83,14 @@ color: blue
|
|||||||
- **Код ответа отражает то, что проект считает событием**, а не удобство
|
- **Код ответа отражает то, что проект считает событием**, а не удобство
|
||||||
реализации. Если инвариант говорит «сохранили — значит приняли», новая ветвь,
|
реализации. Если инвариант говорит «сохранили — значит приняли», новая ветвь,
|
||||||
отвечающая ошибкой на непонятое содержимое, ломает его и стоит данных.
|
отвечающая ошибкой на непонятое содержимое, ломает его и стоит данных.
|
||||||
|
- **Текст ошибки и «заикание» слоёв.** Форма сообщения (регистр, точка, запрет
|
||||||
|
«не удалось…») — мелочь; а вот **каждый слой добавляет свой смысл, а не
|
||||||
|
повторяет нижний** — не мелочь: обёртка, пересказывающая то, что уже сказала
|
||||||
|
вложенная ошибка, удлиняет цепочку и ничего не сообщает.
|
||||||
|
- **Граница паники.** Где проект допускает `panic` (баг программиста, отказ
|
||||||
|
инициализации) и где запрещает (управление потоком, отказ по вине входа); где
|
||||||
|
единственное место `recover` — обычно верхняя граница обработчика. Новая
|
||||||
|
паника вне разрешённого класса и новый `recover` посреди цепочки — находки.
|
||||||
- **Sentinel против типизированной ошибки.** Тип заводим, когда вызывающему нужны
|
- **Sentinel против типизированной ошибки.** Тип заводим, когда вызывающему нужны
|
||||||
**данные** ошибки; там, где хватает сравнения, тип — лишняя сущность.
|
**данные** ошибки; там, где хватает сравнения, тип — лишняя сущность.
|
||||||
Независимые ошибки собираются вместе. Глушение ошибки без лога — только с
|
Независимые ошибки собираются вместе. Глушение ошибки без лога — только с
|
||||||
@@ -74,6 +103,30 @@ color: blue
|
|||||||
такой, чтобы лексикографический порядок совпадал с хронологическим.
|
такой, чтобы лексикографический порядок совпадал с хронологическим.
|
||||||
- **Схема и миграции.** Изменение структуры сопровождается обновлением её
|
- **Схема и миграции.** Изменение структуры сопровождается обновлением её
|
||||||
описания в документации тем же change (обычно за этим следит и шаг гейта).
|
описания в документации тем же change (обычно за этим следит и шаг гейта).
|
||||||
|
- **Транзиентный ответ против персистентной диагностики.** Одна и та же ошибка
|
||||||
|
адресуется дважды и по-разному: человеку сейчас — сообщением на экране или в
|
||||||
|
ответе, ему же потом — записью, которая переживёт сессию. Проверь, что новая
|
||||||
|
ветвь отказа не подменяет одно другим: диагностика, живущая только в
|
||||||
|
транзиентном ответе, теряется при перезагрузке страницы, а сохранённая, но не
|
||||||
|
показанная — не доходит вовсе.
|
||||||
|
- **Канонический вид значения и нормализация на границах.** Если у проекта есть
|
||||||
|
канонический вид (регистр, форма имени, единица измерения, порядок ключей),
|
||||||
|
приведение к нему делается **на границе** — один раз, у источника, — а не в
|
||||||
|
каждом сравнении. Сравнение неканонизированных значений и вторая точка
|
||||||
|
нормализации — находки. Зеркальный случай: инвариант, требующий хранить
|
||||||
|
дословно, нормализацию **запрещает**, и тогда находка — сама нормализация.
|
||||||
|
- **Естественные и составные ключи.** Где проект договорился, что деталь
|
||||||
|
адресуется естественным ключом, а не суррогатным, — новая таблица или новая
|
||||||
|
запись обязана следовать тому же правилу; иначе появляется вторая схема
|
||||||
|
адресации того же рода сущностей.
|
||||||
|
- **Вызовы внешних сервисов логируются все.** Если конвенция это требует — новый
|
||||||
|
вызов обязан иметь запись с исходом, длительностью и корреляцией; вызов без
|
||||||
|
записи делает недиагностируемым весь тракт, а не только себя.
|
||||||
|
- **Шаблоны и разметка: единый источник.** Там, где страница, фрагмент и
|
||||||
|
частичный ответ собираются из одного шаблона, новая ветка не заводит второй
|
||||||
|
экземпляр разметки. Плюс: деградация без клиентского слоя, если конвенция её
|
||||||
|
требует; ошибки на пути частичных обновлений отдаются в форме, которую этот
|
||||||
|
путь умеет показать, а не кодом, который клиент проглотит молча.
|
||||||
- **Тесты разбора — на реальных данных**, а не на придуманных, и с проверкой
|
- **Тесты разбора — на реальных данных**, а не на придуманных, и с проверкой
|
||||||
идемпотентности повторного разбора.
|
идемпотентности повторного разбора.
|
||||||
|
|
||||||
@@ -102,8 +155,8 @@ color: blue
|
|||||||
## Формат вывода
|
## Формат вывода
|
||||||
|
|
||||||
Находки по контракту. Если конвенции нарушены не были — так и напиши, перечислив
|
Находки по контракту. Если конвенции нарушены не были — так и напиши, перечислив
|
||||||
**проверенные разделы файла конвенций** (без этого «замечаний нет» ничего не
|
**прочитанные файлы конвенций и проверенные разделы каждого** (без этого
|
||||||
значит). В конце — обязательный блок:
|
«замечаний нет» ничего не значит). В конце — обязательный блок:
|
||||||
|
|
||||||
```
|
```
|
||||||
## Coverage of this pass
|
## Coverage of this pass
|
||||||
|
|||||||
@@ -22,10 +22,12 @@ color: red
|
|||||||
шагов, что означает каждый исход, **какие шаги красят безусловно и почему**, и
|
шагов, что означает каждый исход, **какие шаги красят безусловно и почему**, и
|
||||||
чего в гейте намеренно нет. Раздел **`## Команды`** — что запускать запрещено.
|
чего в гейте намеренно нет. Раздел **`## Команды`** — что запускать запрещено.
|
||||||
|
|
||||||
Брифа нет — найди команду гейта сама (`Taskfile.yml`, `Makefile`, `justfile`,
|
**Брифа нет** — найди команду гейта сама (`Taskfile.yml`, `Makefile`, `justfile`,
|
||||||
`scripts/`), выполни её и **скажи в границах покрытия, что состав шагов и их
|
`scripts/`) и выполни её, но: `critical` по основанию «нарушен инвариант проекта»
|
||||||
цену ты вывела из конфига, а не из брифа**: шаг, красящий безусловно, ты в этом
|
не присваивай — severity безусловного шага назначает бриф, а в этом режиме ты не
|
||||||
режиме от обычного не отличишь.
|
отличишь такой шаг от обычного. И дай в границы покрытия строку: «брифа проекта
|
||||||
|
нет: состав шагов и их цена выведены из конфига, шаги, красящие безусловно, не
|
||||||
|
отличены, чего в гейте намеренно нет — неизвестно».
|
||||||
|
|
||||||
## Что делаешь
|
## Что делаешь
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: review-ops
|
name: review-ops
|
||||||
description: "Эксплуатационный проход ревью — пишет постмортем «это упало через неделю на проде» от симптома у владельца сервиса к строке кода. Обязательные вопросы: рост объёма, деградация окружения и внешних зависимостей, повторная и одновременная операция, частичный откат при двух версиях, миграция под живым потоком, отмена контекста на середине, наблюдаемость и тишина, поведение библиотеки и драйвера в вырожденном случае. Формулирует условиями, а не утверждениями — реального профиля нагрузки не знает. Только чтение."
|
description: "Эксплуатационный проход ревью — пишет постмортем «это упало через неделю на проде» от симптома у владельца сервиса к строке кода. Обязательные вопросы: рост объёма, деградация окружения и внешних зависимостей, повторная и одновременная операция, частичный откат при двух версиях, миграция под живым потоком, отмена контекста на середине, наблюдаемость и тишина, поведение библиотеки и драйвера в вырожденном случае, чтение узлом состояния, которое он сам же меняет. Формулирует условиями, а не утверждениями — реального профиля нагрузки не знает. Только чтение."
|
||||||
tools: Read, Grep, Glob, Bash
|
tools: Read, Grep, Glob, Bash
|
||||||
model: sonnet
|
model: sonnet
|
||||||
color: yellow
|
color: yellow
|
||||||
@@ -30,8 +30,16 @@ color: yellow
|
|||||||
нет. Тогда постмортем про «недосчитались данных» весит больше, чем про «сервис
|
нет. Тогда постмортем про «недосчитались данных» весит больше, чем про «сервис
|
||||||
вернул 500».
|
вернул 500».
|
||||||
|
|
||||||
Брифа нет — задавай те же вопросы, но **все** ответы формулируй условиями и
|
Ещё берёшь: **`## Прецеденты`** — что в этом проекте уже ломалось и чем это было
|
||||||
скажи в границах покрытия, что профиль эксплуатации неизвестен.
|
воспроизведено (готовый оракул и готовая проба для вопроса 8);
|
||||||
|
**`## Вопросы к проходам`** — если там есть блок `ops`, эти вопросы задаются
|
||||||
|
дополнительно к обязательным и ответы на них выводятся явно.
|
||||||
|
|
||||||
|
**Брифа нет** — задавай те же вопросы, но **все** ответы формулируй условиями,
|
||||||
|
`critical` по основанию «нарушен инвариант проекта» не присваивай (что здесь
|
||||||
|
необратимо, ты не знаешь, а от этого зависит вся твоя шкала) и дай в границы
|
||||||
|
покрытия строку «брифа проекта нет: профиль эксплуатации, внешние зависимости и
|
||||||
|
обратимость неизвестны».
|
||||||
|
|
||||||
## Метод: постмортем от симптома
|
## Метод: постмортем от симптома
|
||||||
|
|
||||||
@@ -81,11 +89,22 @@ color: yellow
|
|||||||
8. **Поведение библиотеки, драйвера и настроек — измеряется, а не вычитывается
|
8. **Поведение библиотеки, драйвера и настроек — измеряется, а не вычитывается
|
||||||
из документации.** Спрашивай: что возвращается в **вырожденном** случае — при
|
из документации.** Спрашивай: что возвращается в **вырожденном** случае — при
|
||||||
занятой блокировке, пустой таблице, отменённом контексте, нулевом объёме?
|
занятой блокировке, пустой таблице, отменённом контексте, нулевом объёме?
|
||||||
Отличим ли этот ответ от штатного? Прецедент, ради которого пункт существует:
|
Отличим ли этот ответ от штатного? Класс, ради которого пункт существует:
|
||||||
контрольная точка журнала под занятой блокировкой возвращала `-1` вместо пары
|
библиотека возвращает в вырожденном случае значение, которое код сравнивает
|
||||||
чисел, и сравнение `-1 >= -1` читалось как «журнал разобран целиком» — 1492
|
тем же оператором, что и штатное, — и отказ читается как успех. Такое из
|
||||||
тика из 5502, найдено экспериментом на стенде, из документации не следовало.
|
документации не следует **никогда**: оно достаётся экспериментом на стенде.
|
||||||
Проверяй на копии или во временном каталоге, рабочие данные не трогай.
|
Проверяй на копии или во временном каталоге, рабочие данные не трогай.
|
||||||
|
Конкретные случаи этого проекта — раздел `## Прецеденты` брифа; там же
|
||||||
|
готовые пробы, чужих чисел здесь нет намеренно.
|
||||||
|
9. **Читает ли узел состояние, которое сам же меняет.** Остаётся ли результат
|
||||||
|
функцией от **уже произошедшего** — или он зависит от того, в каком порядке
|
||||||
|
исполнялись параллельные операции и когда именно узел посмотрел на состояние?
|
||||||
|
Ищи: решение принимается по прочитанному значению, которое к моменту записи
|
||||||
|
уже другое; счётчик или курсор, который узел одновременно читает и двигает;
|
||||||
|
ветка, выбираемая по «сколько сейчас лежит в таблице»; повторный прогон,
|
||||||
|
дающий другой результат на тех же входных событиях. Это тот же вопрос, что
|
||||||
|
рубрика задаёт дизайну до кода, — но задать его **на коде** больше некому:
|
||||||
|
рубрика на код не смотрит.
|
||||||
|
|
||||||
## Правило формулировки
|
## Правило формулировки
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,23 @@ color: purple
|
|||||||
`${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/finding-contract.md`
|
`${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/finding-contract.md`
|
||||||
(точный путь конвейер передаёт в задании).
|
(точный путь конвейер передаёт в задании).
|
||||||
|
|
||||||
|
## Что берёшь из брифа
|
||||||
|
|
||||||
|
**`## Проект`** — граница домена: твоя версия должна лежать по ту же сторону, что
|
||||||
|
и существующая, иначе весь дифф по решениям окажется спором о scope.
|
||||||
|
**`## Инварианты`** — то, что твоя реализация обязана соблюсти (дословность
|
||||||
|
хранения, «сохранили — значит приняли» и подобное). **`## Прод и поток`** — объёмы
|
||||||
|
и представление данных: решение, разумное на сотне записей, неразумно на
|
||||||
|
миллионе. **`## Карта`** — где конвенции и где файл наблюдений на живых данных.
|
||||||
|
|
||||||
|
**Брифа нет** — пиши свою версию по спеке и конвенциям, но: `critical` по
|
||||||
|
основанию «нарушен инвариант проекта» не присваивай (инвариантов ты не знаешь, а
|
||||||
|
именно они чаще всего объясняют чужое решение), объёмы не предполагай и в границы
|
||||||
|
покрытия дай строку «брифа проекта нет: инварианты и профиль нагрузки прогону
|
||||||
|
неизвестны, расхождения по этим основаниям не оценивались». Без брифа риск
|
||||||
|
конкретно этого прохода максимален: твоя версия проще, потому что не знает, чего
|
||||||
|
проект боится.
|
||||||
|
|
||||||
**Тебя запускают по триггеру, а не всегда.** Триггер: изменение вводит **новое
|
**Тебя запускают по триггеру, а не всегда.** Триггер: изменение вводит **новое
|
||||||
правило идентичности, слияния или разбора** (проектная формулировка — в разделе
|
правило идентичности, слияния или разбора** (проектная формулировка — в разделе
|
||||||
`## Триггеры` брифа). Вне его твой счёт — самый большой в конвейере (он
|
`## Триггеры` брифа). Вне его твой счёт — самый большой в конвейере (он
|
||||||
|
|||||||
@@ -21,9 +21,14 @@ color: purple
|
|||||||
Это материал для требования «минимум три пункта специфичны для типа узла».
|
Это материал для требования «минимум три пункта специфичны для типа узла».
|
||||||
- **`## Инварианты`** и **`## Проект`** — чтобы рубрика не противоречила тому, что
|
- **`## Инварианты`** и **`## Проект`** — чтобы рубрика не противоречила тому, что
|
||||||
проект защищает и чем он себя ограничил.
|
проект защищает и чем он себя ограничил.
|
||||||
|
- **`## Прецеденты`** — классы дефектов, уже случавшихся здесь: свойство,
|
||||||
|
сформулированное по прецеденту, сильнее любого общего.
|
||||||
|
|
||||||
Разделов нет — порождай рубрику по общей практике и скажи в границах покрытия,
|
**Брифа или этих разделов нет** — порождай рубрику по общей практике, но
|
||||||
что специфика узла в проекте не описана: часть пунктов неизбежно окажется общими.
|
`critical` по основанию «нарушен инвариант проекта» (в фазе 2) не присваивай и
|
||||||
|
дай в границы покрытия строку: «брифа проекта нет: рода узлов, инварианты и
|
||||||
|
прецеденты неизвестны; требование «минимум три пункта специфичны для типа узла»
|
||||||
|
выполнено по общей практике, а не по этому проекту».
|
||||||
|
|
||||||
## Порядок фаз обязателен
|
## Порядок фаз обязателен
|
||||||
|
|
||||||
@@ -68,10 +73,13 @@ color: purple
|
|||||||
если вся рубрика — пересказ инвариантов из брифа, проход выродился в
|
если вся рубрика — пересказ инвариантов из брифа, проход выродился в
|
||||||
applicative;
|
applicative;
|
||||||
- **отдельным пунктом — узел, читающий состояние, которое сам же меняет.**
|
- **отдельным пунктом — узел, читающий состояние, которое сам же меняет.**
|
||||||
Спроси, остаётся ли результат функцией от того, что уже произошло, а не от
|
Спроси, остаётся ли результат функцией от того, что **уже произошло**, а не от
|
||||||
того, что произойдёт: правило родилось из дефекта, где запрос брал последнее
|
того, в каком порядке исполнялись параллельные операции и когда именно узел
|
||||||
выведенное значение **вообще**, а не последнее предшествующее, и пересборка
|
посмотрел на состояние. Класс: запрос берёт «последнее выведенное значение»
|
||||||
переставала воспроизводить состояние.
|
вообще вместо последнего предшествующего — и пересборка перестаёт
|
||||||
|
воспроизводить состояние. Случаи этого проекта — в разделе `## Прецеденты`
|
||||||
|
брифа. Тот же вопрос на **готовом коде** задаёт эксплуатационный проход
|
||||||
|
(вопрос 9); здесь он задаётся дизайну.
|
||||||
|
|
||||||
Выведи рубрику **до** любых находок. Она — часть результата, даже если код
|
Выведи рубрику **до** любых находок. Она — часть результата, даже если код
|
||||||
окажется идеальным.
|
окажется идеальным.
|
||||||
|
|||||||
@@ -19,13 +19,18 @@ Development на OpenSpec). Оптика — требования, а не ст
|
|||||||
|
|
||||||
- **`## Инварианты`** — по ним проверяется, отражены ли в спеке задетые свойства,
|
- **`## Инварианты`** — по ним проверяется, отражены ли в спеке задетые свойства,
|
||||||
и по ним же присваивается severity. Цитируй пункт дословно, когда ссылаешься.
|
и по ним же присваивается severity. Цитируй пункт дословно, когда ссылаешься.
|
||||||
- **`## Карта`** — где актуальные спеки, где дельты, где архитектура и где лежат
|
- **`## Карта`** — где актуальные спеки, где дельты, где архитектура и **где файл
|
||||||
наблюдения о реальном поведении внешних систем.
|
наблюдений на живых данных**. Там же — **нарезка capability и миграционное
|
||||||
|
состояние спек**: по какому признаку проект режет capability и какие темы ещё
|
||||||
|
не переехали из документации в спеки. Без этого пункта непереехавшая тема
|
||||||
|
читается как пробел в спеке, и находка уходит в пустоту.
|
||||||
- **`## Проект`** — граница домена: требование, переносящее понятие через неё, —
|
- **`## Проект`** — граница домена: требование, переносящее понятие через неё, —
|
||||||
находка в спеку, а не в код.
|
находка в спеку, а не в код.
|
||||||
|
|
||||||
Брифа нет — сверяй только спеку с кодом, `critical` по основанию «нарушен
|
**Брифа нет** — сверяй только спеку с кодом, `critical` по основанию «нарушен
|
||||||
инвариант» не присваивай и скажи об этом в границах покрытия.
|
инвариант проекта» не присваивай и дай в границы покрытия строку: «брифа проекта
|
||||||
|
нет: инварианты, граница домена и состояние переноса capability в спеки
|
||||||
|
неизвестны; отражение инвариантов в спеке не проверялось».
|
||||||
|
|
||||||
## Источник требований
|
## Источник требований
|
||||||
|
|
||||||
|
|||||||
@@ -27,8 +27,19 @@ color: green
|
|||||||
|
|
||||||
Из брифа тебе нужны: **`## Инварианты`** (что делает находку `critical` и что
|
Из брифа тебе нужны: **`## Инварианты`** (что делает находку `critical` и что
|
||||||
делает её развилкой), **`## Прод и поток`** (что необратимо — от этого зависит
|
делает её развилкой), **`## Прод и поток`** (что необратимо — от этого зависит
|
||||||
ранжирование), **`## Недоступно проверке`** (эта секция целиком уезжает в границы
|
ранжирование), **`## Прецеденты`** (готовые оракулы: находка того же класса, что
|
||||||
покрытия), **`## Команды`** (что запускать запрещено).
|
уже воспроизводился здесь, подтверждается ссылкой на прецедент),
|
||||||
|
**`## Типовые ложноположительные`** (единственный проектный вход в шаг 4),
|
||||||
|
**`## Недоступно проверке`** — оба подраздела, они целиком уезжают в границы
|
||||||
|
покрытия и **не сливаются в один список**, — **`## Команды`** (что запускать
|
||||||
|
запрещено).
|
||||||
|
|
||||||
|
**Брифа нет** — работай по общим правилам, но: ни одну находку не поднимай до
|
||||||
|
`critical` по основанию «нарушен инвариант проекта» (сослаться не на что),
|
||||||
|
ранжируй по обратимости, выведенной из кода, и назови это предположением. Первой
|
||||||
|
строкой сводки — «прогон шёл без брифа проекта (<причина>)», и это же идёт в
|
||||||
|
границы покрытия. Одинаковая строка «брифа нет» без причины перестаёт читаться
|
||||||
|
на третьей задаче — причину сохраняй.
|
||||||
|
|
||||||
## Порядок. Не меняй его
|
## Порядок. Не меняй его
|
||||||
|
|
||||||
@@ -79,9 +90,16 @@ severity:
|
|||||||
|
|
||||||
Типовая вкусовщина в выводах generative-проходов: переименования без коллизии,
|
Типовая вкусовщина в выводах generative-проходов: переименования без коллизии,
|
||||||
перестановка функций, «лучше вынести в отдельный файл», предложения обобщить
|
перестановка функций, «лучше вынести в отдельный файл», предложения обобщить
|
||||||
работающий частный случай. Отдельный класс — предложение «нормализовать» то, что
|
работающий частный случай.
|
||||||
инвариант проекта велит хранить дословно: это не просто вкусовщина, а нарушение
|
|
||||||
инварианта, и выбрасывать его надо с пометкой почему.
|
**Проектный вход сюда один — раздел `## Типовые ложноположительные` брифа.**
|
||||||
|
Там перечислены находки, которые в этом проекте выглядят убедительно и всегда
|
||||||
|
неверны: они выбрасываются со ссылкой на пункт и с пометкой почему, а не
|
||||||
|
«смягчаются». Классический обитатель раздела — предложение «нормализовать» то,
|
||||||
|
что инвариант велит хранить дословно: это не просто вкусовщина, а находка,
|
||||||
|
предлагающая нарушить инвариант. Раздела нет или он пуст — скажи об этом строкой
|
||||||
|
в границах покрытия: отсев шёл по общим критериям, проектных ложноположительных
|
||||||
|
ты не знал.
|
||||||
|
|
||||||
### 5. Ранжирование по ущербу × вероятности
|
### 5. Ранжирование по ущербу × вероятности
|
||||||
|
|
||||||
@@ -121,8 +139,7 @@ severity:
|
|||||||
Сводка отчёта называет **каждый проход профиля** и его исход: отработал (сколько
|
Сводка отчёта называет **каждый проход профиля** и его исход: отработал (сколько
|
||||||
находок) / не запускался (почему). Сверь список запущенного с составом профиля
|
находок) / не запускался (почему). Сверь список запущенного с составом профиля
|
||||||
сам, а не доверяй тому, что тебе подали: пропуск прохода **не отличим от прохода
|
сам, а не доверяй тому, что тебе подали: пропуск прохода **не отличим от прохода
|
||||||
без находок**, и однажды это стоило семи находок и отдельной задачи на их
|
без находок**, и назвать его больше некому.
|
||||||
дозакрытие.
|
|
||||||
|
|
||||||
Расхождение состава с профилем — это находка о прогоне, и она идёт в сводку
|
Расхождение состава с профилем — это находка о прогоне, и она идёт в сводку
|
||||||
первой строкой, а не растворяется в границах покрытия.
|
первой строкой, а не растворяется в границах покрытия.
|
||||||
@@ -135,12 +152,16 @@ severity:
|
|||||||
- какие **не** запускались и почему (профиль, бюджет, недоступный инструмент,
|
- какие **не** запускались и почему (профиль, бюджет, недоступный инструмент,
|
||||||
остановленный прогон);
|
остановленный прогон);
|
||||||
- что каждый запущенный проход **не мог проверить в принципе** — из его charter'а;
|
- что каждый запущенный проход **не мог проверить в принципе** — из его charter'а;
|
||||||
- **что осталось целиком на человеке** — раздел `## Недоступно проверке` брифа
|
- **что осталось целиком на человеке** — раздел `## Недоступно проверке` брифа,
|
||||||
целиком, плюс: история инцидентов, поведение под реальным потоком, поведение
|
**двумя отдельными списками**: «не проверит ни один проход» и «перестали
|
||||||
внешних систем в их версиях, завязка потребителей на текущее поведение и вопрос
|
проверять сознательно». Слитый список бесполезен: при следующем промахе первый
|
||||||
«а нужна ли эта функциональность вообще»;
|
вопрос — «не тот ли это класс, который мы перестали проверять», и ответить на
|
||||||
- если брифа не было — строку об этом: инварианты, модель угроз и профиль
|
него можно только если второй список виден отдельно. Плюс общее: история
|
||||||
нагрузки прогону были неизвестны.
|
инцидентов, поведение под реальным потоком, поведение внешних систем в их
|
||||||
|
версиях, завязка потребителей на текущее поведение и вопрос «а нужна ли эта
|
||||||
|
функциональность вообще»;
|
||||||
|
- если брифа не было — строку об этом **с причиной**: инварианты, модель угроз и
|
||||||
|
профиль нагрузки прогону были неизвестны, потому что <причина>.
|
||||||
|
|
||||||
Формулировка «критичных проблем не обнаружено» **запрещена** без этой секции: она
|
Формулировка «критичных проблем не обнаружено» **запрещена** без этой секции: она
|
||||||
потребляет ощущение проверенности, ничего не гарантируя, и это хуже, чем
|
потребляет ощущение проверенности, ничего не гарантируя, и это хуже, чем
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
---
|
||||||
|
name: project-brief
|
||||||
|
description: Заводит или обновляет бриф ревью проекта (docs/review-brief.md) — файл, откуда конвейер ревью берёт инварианты, команду гейта, модель угроз, объёмы, прецеденты и карту проекта. Вызывать, когда брифа нет (это обнаруживают review-pipeline, task-pipeline и task-batch на старте), когда сменился гейт или появилась новая зависимость, и по прямой просьбе завести или обновить бриф.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Заведение брифа проекта
|
||||||
|
|
||||||
|
Бриф — **предмет** ревью: что здесь нельзя нарушать, чем краснеет гейт, сколько
|
||||||
|
данных реально проходит, что необратимо. Без него конвейер работает в
|
||||||
|
деградированном режиме: `critical` по основанию «нарушен инвариант проекта»
|
||||||
|
недоступен ни одному проходу, числа объёма не используются, архитектурный проход
|
||||||
|
теряет свой главный критерий (граница домена) и вырождается в общее мнение.
|
||||||
|
|
||||||
|
Поэтому заведение брифа — **шаг, а не документ**. Этот скилл его выполняет.
|
||||||
|
|
||||||
|
- Контракт разделов — [контракт брифа](../review-pipeline/references/project-brief.md).
|
||||||
|
- Форма и образцы заполнения — [шаблон](../review-pipeline/references/brief-template.md).
|
||||||
|
|
||||||
|
## Когда вызывается
|
||||||
|
|
||||||
|
- **Автоматически**, без спроса: `av-dev-pipeline:review-pipeline`,
|
||||||
|
`av-dev-pipeline:task-pipeline` и `av-dev-pipeline:task-batch` разрешают путь к
|
||||||
|
брифу на старте и, не найдя его ни по одному пути, зовут этот скилл. Это не
|
||||||
|
развилка и не повод остановиться — заведение брифа делается молча, как любая
|
||||||
|
другая механика.
|
||||||
|
- **По событию:** сменился гейт; появился новый контур, зависимость или источник
|
||||||
|
входа; в журнал ревью попала запись вида «проход не мог этого знать»; свойство
|
||||||
|
промоутнулось в правило линтера (тогда пункт из брифа **вычёркивается**).
|
||||||
|
- **По просьбе человека.**
|
||||||
|
|
||||||
|
Планового пересмотра нет.
|
||||||
|
|
||||||
|
## Шаг 1. Убедиться, что брифа действительно нет
|
||||||
|
|
||||||
|
Порядок разрешения пути — тот же, что у конвейера:
|
||||||
|
|
||||||
|
1. путь, названный в задании;
|
||||||
|
2. `docs/review-brief.md`;
|
||||||
|
3. `.claude/review-brief.md`.
|
||||||
|
|
||||||
|
Файл есть, но неполон (нет обязательного раздела, раздел пуст, числа без
|
||||||
|
провенанса) — это **не** заведение с нуля: дозаполняй недостающее и не переписывай
|
||||||
|
то, что уже выверено. Разошедшийся бриф хуже отсутствующего, но переписанный
|
||||||
|
поверх выверенного — хуже разошедшегося.
|
||||||
|
|
||||||
|
## Шаг 2. Собрать материал из проекта
|
||||||
|
|
||||||
|
Бриф **выводится из проекта, а не сочиняется**. Источники по убыванию плотности:
|
||||||
|
|
||||||
|
| Раздел брифа | Откуда берётся |
|
||||||
|
|---|---|
|
||||||
|
| `## Проект` | `CLAUDE.md` / `AGENTS.md`, паспорт или README — абзац «что это и чего оно не делает» |
|
||||||
|
| `## Инварианты` | раздел инвариантов `CLAUDE.md`, архитектура, журнал решений; **цитируются формулировкой** |
|
||||||
|
| `## Гейт` | `Taskfile.yml` / `Makefile` / `justfile` / CI — сама цель гейта, состав её шагов, коды и логи |
|
||||||
|
| `## Команды` | тот же файл задач: карта проекта, поднять вживую, тесты, дорогое вне гейта, запрещённое |
|
||||||
|
| `## Прод и поток` | документация по деплою и архитектуре, конфиг и его образец, схема БД, файл наблюдений на живых данных |
|
||||||
|
| `## Модель угроз` | конфиг (токены, права), раскладка файлов на диске, схема ключей, места приёма недоверенного входа |
|
||||||
|
| `## Карта` | дерево репозитория: спеки, конвенции, архитектура, журнал ревью, миграции, `testdata`, основная ветка |
|
||||||
|
| `## Типовые узлы` | дерево пакетов: какие рода узлов реально есть |
|
||||||
|
| `## Прецеденты` | журнал ревью, архивные отчёты триажа, `git log` по починкам |
|
||||||
|
| `## Недоступно проверке` | журнал ревью (что решили не проверять) плюс общий список из контракта |
|
||||||
|
|
||||||
|
Прочитай `CLAUDE.md` и всё, на что он ссылается, **до** того, как писать первую
|
||||||
|
строку. Бриф, собранный из одного файла, повторяет его и потому бесполезен.
|
||||||
|
|
||||||
|
## Шаг 3. Заполнить
|
||||||
|
|
||||||
|
Идёшь по контракту раздел за разделом. Четыре правила ведения, из-за которых
|
||||||
|
брифы портятся чаще всего:
|
||||||
|
|
||||||
|
1. **Не пересказывай документацию.** Факт, записанный в `CLAUDE.md` или в
|
||||||
|
архитектуре, попадает сюда ссылкой и одной строкой сути. Исключение — раздел
|
||||||
|
инвариантов: он цитируется дословно, потому что по нему присваивается severity.
|
||||||
|
2. **Числа — с провенансом.** «Тела доходили до 42 МБ (замер,
|
||||||
|
`docs/local-research.md`)». Число без источника проход обязан превратить в
|
||||||
|
условие, то есть оно бесполезно.
|
||||||
|
3. **Пустой пункт называется пустым.** «Внешних зависимостей нет — смотри на диск
|
||||||
|
и на СУБД» стоит целого прохода: без этой строки эксплуатационный проход
|
||||||
|
потратит обязательный вопрос впустую или выдумает зависимость. То же про
|
||||||
|
угрозы вне модели, про отсутствующие прецеденты, про отсутствие наблюдателя.
|
||||||
|
4. **Не выдумывай четыре вещи.** Измеренные числа; периметр модели угроз; то, что
|
||||||
|
в этом проекте необратимо; и **кто обязан гонять дорогую проверку вне гейта**
|
||||||
|
— всё это из кода не выводится. Не нашёл в документации — **спроси человека на
|
||||||
|
шаге 4**, а до ответа напиши пункт словом «неизвестно» с пометкой, что он ждёт
|
||||||
|
ответа. Придуманное число здесь дороже отсутствующего: проход сошлётся на него
|
||||||
|
как на замер.
|
||||||
|
5. **Что выведено, а не прочитано, — помечай.** Чаще всего это severity у
|
||||||
|
инвариантов: проекты редко пишут её рядом с формулировкой, и её приходится
|
||||||
|
выводить по обратимости последствия. Пометка «выведена по обратимости» стоит
|
||||||
|
трёх слов и сообщает проходу, чьё это суждение, — а он по ней ставит
|
||||||
|
`critical`. То же для периметра, восстановленного из конфига, и для чисел, чей
|
||||||
|
источник по ссылке не подтвердился.
|
||||||
|
|
||||||
|
## Шаг 4. Показать человеку
|
||||||
|
|
||||||
|
Бриф — единственный файл, который конвейер **читает как истину**, поэтому он
|
||||||
|
показывается, а не заводится молча:
|
||||||
|
|
||||||
|
- покажи готовый файл (или дифф, если это обновление);
|
||||||
|
- отдельным коротким списком назови, **что выведено из проекта**, а что
|
||||||
|
**предположено или осталось неизвестным** — по этим строкам человек и правит;
|
||||||
|
- если на шаге 3 остались вопросы из класса «не выдумывай три вещи», задай их
|
||||||
|
здесь, разом и с вариантами.
|
||||||
|
|
||||||
|
Ответа ждать не обязательно: работа продолжается по заведённому брифу, а
|
||||||
|
неизвестные пункты честно стоят словом «неизвестно» — проход прочитает его как
|
||||||
|
деградацию по этому пункту, а не как факт.
|
||||||
|
|
||||||
|
**Бриф ведёт проект.** Файл кладётся в репозиторий проекта и коммитится вместе с
|
||||||
|
той работой, в ходе которой заведён. Плагин его больше не правит — он только
|
||||||
|
читает.
|
||||||
|
|
||||||
|
## Шаг 5. Вернуться в вызвавший шаг
|
||||||
|
|
||||||
|
Скажи вызвавшему скиллу путь к брифу — дальше конвейер передаёт его каждому
|
||||||
|
проходу готовым, и деградированный режим не включается.
|
||||||
|
|
||||||
|
## Если завести нельзя
|
||||||
|
|
||||||
|
Заведение отменяется ровно в трёх случаях: репозиторий доступен только на чтение;
|
||||||
|
человек прямо сказал брифа не заводить; проект настолько чужой, что вывести
|
||||||
|
инварианты неоткуда. Тогда — деградированный режим по контракту: строка в границы
|
||||||
|
покрытия и запрет на `critical` по основанию «нарушен инвариант проекта».
|
||||||
|
|
||||||
|
Во всех остальных случаях бриф заводится. «Задача маленькая, брифа не надо» —
|
||||||
|
не основание: бриф заводится один раз на проект, а деградированный режим платит
|
||||||
|
на каждой задаче.
|
||||||
@@ -29,28 +29,61 @@ description: Конвейер ревью изменения — детермин
|
|||||||
обнаружено» потребляет ощущение проверенности, ничего не гарантируя. Секция
|
обнаружено» потребляет ощущение проверенности, ничего не гарантируя. Секция
|
||||||
границ покрытия обязательна и не сокращается — в том числе в докладе человеку.
|
границ покрытия обязательна и не сокращается — в том числе в докладе человеку.
|
||||||
|
|
||||||
|
## Предпосылки
|
||||||
|
|
||||||
|
Конвейер опирается на внешнюю обвязку и без неё работает не целиком. Проверь это
|
||||||
|
один раз, при установке плагина в проект:
|
||||||
|
|
||||||
|
- **OpenSpec и скиллы `opsx:*`.** Профиль `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, либо сознательно не зовёт
|
||||||
|
`review-specs` и профиль `design` — и тогда это идёт строкой «не запускался» в
|
||||||
|
границы покрытия, как любой другой пропуск.
|
||||||
|
- **Бриф проекта** — см. следующий раздел. Заводится скиллом, а не руками.
|
||||||
|
- **Проектные копии этих скиллов и агентов удаляются при установке.** Если в
|
||||||
|
проекте уже лежат свои `.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-pipeline:project-brief`.
|
||||||
|
|
||||||
## Что конвейер защищает — приходит из брифа
|
## Что конвейер защищает — приходит из брифа
|
||||||
|
|
||||||
Проходы общие, а нарушать нельзя проектное. Список инвариантов, команду гейта,
|
Проходы общие, а нарушать нельзя проектное. Список инвариантов, команду гейта,
|
||||||
объёмы и модель угроз конвейер **не знает** — он читает их в брифе проекта:
|
объёмы, прецеденты и модель угроз конвейер **не знает** — он читает их в брифе
|
||||||
[references/project-brief.md](references/project-brief.md) описывает контракт,
|
проекта: [references/project-brief.md](references/project-brief.md) описывает
|
||||||
[references/brief-template.md](references/brief-template.md) — образец
|
контракт, [references/brief-template.md](references/brief-template.md) — образец
|
||||||
заполнения.
|
заполнения.
|
||||||
|
|
||||||
Разреши путь к брифу один раз, в начале прогона: путь из задания →
|
Разреши путь к брифу один раз, в начале прогона: путь из задания →
|
||||||
`docs/review-brief.md` → `.claude/review-brief.md`. Дальше передавай готовым.
|
`docs/review-brief.md` → `.claude/review-brief.md`. Дальше передавай готовым.
|
||||||
|
|
||||||
**Брифа нет — прогон идёт в деградированном режиме**: `critical` по основанию
|
**Брифа нет по всем трём путям — заведи его, а не понижай прогон.** Вызови Skill
|
||||||
«нарушен инвариант проекта» никем не присваивается, числа объёма не
|
**`av-dev-pipeline:project-brief`**: он соберёт бриф из `CLAUDE.md`, архитектуры,
|
||||||
используются, и в границы покрытия уезжает строка «брифа проекта нет». Это дыра
|
файла задач и конвенций, покажет человеку и вернёт путь. Это механика, а не
|
||||||
покрытия, а не нейтральное умолчание.
|
развилка: спрашивать разрешения не нужно, и остановка прогона тут не
|
||||||
|
предусмотрена. Заведение стоит одного шага один раз на проект — деградированный
|
||||||
|
режим платит на каждой задаче.
|
||||||
|
|
||||||
|
**Деградированный режим — исход, а не умолчание.** Он включается ровно тогда,
|
||||||
|
когда бриф завести не удалось (репозиторий на чтение, человек прямо запретил,
|
||||||
|
инварианты вывести неоткуда): `critical` по основанию «нарушен инвариант
|
||||||
|
проекта» никем не присваивается, числа объёма не используются, и в границы
|
||||||
|
покрытия уезжает строка «брифа проекта нет, завести не удалось: <причина>».
|
||||||
|
Причина обязательна — без неё строка неотличима от «мы просто не стали».
|
||||||
|
|
||||||
## Что получает каждый проход
|
## Что получает каждый проход
|
||||||
|
|
||||||
Задание любому проходу состоит из шести вещей, и первые две без брифа
|
Задание любому проходу состоит из шести вещей, и первая — главная: без брифа
|
||||||
бессмысленны:
|
проход теряет предмет проверки и уходит в деградированный режим.
|
||||||
|
|
||||||
- **бриф** — путь;
|
- **бриф** — путь (разрешён или заведён на старте, см. выше);
|
||||||
- **контракт находок** — путь к
|
- **контракт находок** — путь к
|
||||||
[references/finding-contract.md](references/finding-contract.md) (в
|
[references/finding-contract.md](references/finding-contract.md) (в
|
||||||
установленном плагине — `${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/`);
|
установленном плагине — `${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/`);
|
||||||
@@ -170,14 +203,14 @@ description: Конвейер ревью изменения — детермин
|
|||||||
Почему умолчание именно такое:
|
Почему умолчание именно такое:
|
||||||
|
|
||||||
- **Замеры.** Проходы `adversary` и `ops` доказывают находки числами: время
|
- **Замеры.** Проходы `adversary` и `ops` доказывают находки числами: время
|
||||||
удержания блокировки, пик кучи, рост файлов журнала, длительность транзакции.
|
удержания блокировки против её таймаута, пик кучи против размера тела, темп
|
||||||
Два меряющих прохода на одной машине соревнуются за диск, CPU и за саму СУБД и
|
роста файлов журнала, длительность транзакции. Два меряющих прохода на одной
|
||||||
выдают числа, которые не воспроизведутся. Это не гипотеза: находки, ради
|
машине соревнуются за диск, CPU и за саму СУБД и выдают числа, которые не
|
||||||
которых правило записано, опираются ровно на такие замеры (5.019 с удержания
|
воспроизведутся. Это не гипотеза: правило выведено из находок, целиком
|
||||||
блокировки при таймауте 5000 мс, пик 768 МиБ на теле 40 МиБ, 7 МБ/с роста
|
державшихся на таких замерах, — у каждого проекта они свои и лежат в разделе
|
||||||
журнала, 1492 тика из 5502). Число, снятое под конкурентную нагрузку от
|
`## Прецеденты` его брифа. Число, снятое под конкурентную нагрузку от соседнего
|
||||||
соседнего прохода, — это находка с испорченным оракулом, а её опровержение
|
прохода, — это находка с испорченным оракулом, а её опровержение стоит дороже
|
||||||
стоит дороже всего выигрыша от параллельности.
|
всего выигрыша от параллельности.
|
||||||
- **Машина одна.** Рядом идёт задача, поднят сервис, гоняется гейт или дорогая
|
- **Машина одна.** Рядом идёт задача, поднят сервис, гоняется гейт или дорогая
|
||||||
проверка проекта.
|
проверка проекта.
|
||||||
- **Ранний выход** возможен только при последовательном прогоне (см. ниже).
|
- **Ранний выход** возможен только при последовательном прогоне (см. ниже).
|
||||||
@@ -205,9 +238,9 @@ description: Конвейер ревью изменения — детермин
|
|||||||
нулевой стадии** — а не «доезжает» остатком по старому коду;
|
нулевой стадии** — а не «доезжает» остатком по старому коду;
|
||||||
- незапущенные проходы идут в границы покрытия строкой «не запускался: прогон
|
- незапущенные проходы идут в границы покрытия строкой «не запускался: прогон
|
||||||
остановлен на <проход> из-за <находка>», поимённо;
|
остановлен на <проход> из-за <находка>», поимённо;
|
||||||
- триаж запускается только на полном прогоне. Отчёт триажа по половине проходов —
|
- триаж запускается только на полном прогоне. Отчёт триажа по половине проходов
|
||||||
ровно тот случай, который уже стоил семи находок: он выглядит полным, потому
|
выглядит полным, потому что агрегирует всё, что ему подали, — это тот же
|
||||||
что агрегирует всё, что ему подали.
|
молчащий пропуск, что и в разделе «Профили».
|
||||||
|
|
||||||
Ранний выход по находке, которая чинится в пределах существующей формы
|
Ранний выход по находке, которая чинится в пределах существующей формы
|
||||||
(`Действие: инлайн`), **не делается**: дешевле дособрать все находки и починить
|
(`Действие: инлайн`), **не делается**: дешевле дособрать все находки и починить
|
||||||
@@ -246,8 +279,9 @@ description: Конвейер ревью изменения — детермин
|
|||||||
- `review-specs` — критерий взят из **дельта-спек предлагаемого изменения**, а не
|
- `review-specs` — критерий взят из **дельта-спек предлагаемого изменения**, а не
|
||||||
из proposal, сообщения коммита или описания задачи. Сверка двунаправленная;
|
из proposal, сообщения коммита или описания задачи. Сверка двунаправленная;
|
||||||
направление `code → spec` важнее.
|
направление `code → spec` важнее.
|
||||||
- `review-code` — критерий взят из файла конвенций проекта (раздел `## Карта`
|
- `review-code` — критерий взят из конвенций проекта: файла или каталога файлов,
|
||||||
брифа), и только та его часть, которая **не выражается правилом**:
|
путь — раздел `## Карта` брифа. Берётся только та их часть, которая **не
|
||||||
|
выражается правилом**:
|
||||||
механизируемое уже проверила стадия 0. Что именно механизировано, тот же раздел
|
механизируемое уже проверила стадия 0. Что именно механизировано, тот же раздел
|
||||||
брифа перечисляет — повторять это проходом вредно.
|
брифа перечисляет — повторять это проходом вредно.
|
||||||
|
|
||||||
@@ -322,7 +356,8 @@ Recall обоих равен длине их источника — это и е
|
|||||||
|
|
||||||
## Профиль `design` — до кода
|
## Профиль `design` — до кода
|
||||||
|
|
||||||
Запускается на шаге ревью спек (шаг 4 скилла `task-pipeline`), когда change уже
|
Запускается на шаге ревью спек (шаг 4 скилла `av-dev-pipeline:task-pipeline`),
|
||||||
|
когда change уже
|
||||||
имеет `proposal.md` и дельта-спеки, но кода ещё нет. Состав:
|
имеет `proposal.md` и дельта-спеки, но кода ещё нет. Состав:
|
||||||
|
|
||||||
1. `review-specs` в режиме «дизайн ДО кода»;
|
1. `review-specs` в режиме «дизайн ДО кода»;
|
||||||
@@ -359,9 +394,11 @@ Recall обоих равен длине их источника — это и е
|
|||||||
держит вопросы (это знает вызвавший пайплайн, а не конвейер). Оркестратор не
|
держит вопросы (это знает вызвавший пайплайн, а не конвейер). Оркестратор не
|
||||||
останавливается: он урезает изменение до остатка и доводит его.
|
останавливается: он урезает изменение до остатка и доводит его.
|
||||||
- Находка не для этого мерджа, но реальная (отложенный `major`, развилка,
|
- Находка не для этого мерджа, но реальная (отложенный `major`, развилка,
|
||||||
решённая «потом») — не теряется: заводится задачей средствами проекта, с
|
решённая «потом»), — не теряется, но **и не заводится здесь**. Конвейер отдаёт
|
||||||
оракулом и провенансом в теле. Мелочь класса `nit` — пачкой, а не записью на
|
её **списком урожая** в отчёте: формулировка, оракул, провенанс (какой проход,
|
||||||
находку.
|
какой change). Заведение задач принадлежит тому, кто ведёт задачи проекта, —
|
||||||
|
у него свой формат, своя нарезка и свои правила дублей. Мелочь класса `nit`
|
||||||
|
идёт в урожай одной пачкой, а не записью на находку.
|
||||||
- `Promote candidates` — по процедуре [references/promote.md](references/promote.md):
|
- `Promote candidates` — по процедуре [references/promote.md](references/promote.md):
|
||||||
находка → конвенция → правило линтера → **удаление из конвенций и из брифа**.
|
находка → конвенция → правило линтера → **удаление из конвенций и из брифа**.
|
||||||
Третий шаг обязателен.
|
Третий шаг обязателен.
|
||||||
@@ -405,6 +442,7 @@ Recall обоих равен длине их источника — это и е
|
|||||||
|
|
||||||
## Ссылки
|
## Ссылки
|
||||||
|
|
||||||
|
- Skill `av-dev-pipeline:project-brief` — заведение и обновление брифа.
|
||||||
- [references/project-brief.md](references/project-brief.md) — контракт брифа проекта.
|
- [references/project-brief.md](references/project-brief.md) — контракт брифа проекта.
|
||||||
- [references/brief-template.md](references/brief-template.md) — шаблон брифа.
|
- [references/brief-template.md](references/brief-template.md) — шаблон брифа.
|
||||||
- [references/finding-contract.md](references/finding-contract.md) — контракт находок.
|
- [references/finding-contract.md](references/finding-contract.md) — контракт находок.
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
# Шаблон брифа проекта
|
# Шаблон брифа проекта
|
||||||
|
|
||||||
Скопируй в `docs/review-brief.md` и заполни. Контракт разделов — в
|
Образец заполнения. Контракт разделов — в
|
||||||
[project-brief.md](project-brief.md); здесь только образец заполнения.
|
[project-brief.md](project-brief.md); заводит бриф по этому образцу скилл
|
||||||
|
`av-dev-pipeline:project-brief` — руками копировать не надо, но читать полезно.
|
||||||
|
|
||||||
Курсивом даны пояснения — их из готового брифа убирают. Примеры взяты из двух
|
Курсивом даны пояснения — их из готового брифа убирают. Примеры взяты из двух
|
||||||
разных проектов (коллектор данных с непрерывным потоком и связующий сервис вокруг
|
разных проектов (коллектор данных с непрерывным потоком и связующий сервис вокруг
|
||||||
@@ -26,7 +27,8 @@
|
|||||||
## Инварианты
|
## Инварианты
|
||||||
|
|
||||||
*Проверяемое свойство + последствие + severity по умолчанию. Цитируются
|
*Проверяемое свойство + последствие + severity по умолчанию. Цитируются
|
||||||
формулировкой.*
|
формулировкой. Severity проект обычно не пишет — тогда она выводится по
|
||||||
|
обратимости и помечается: «по умолчанию `critical` (выведена по обратимости)».*
|
||||||
|
|
||||||
- **Точка сохраняется дословно.** Незнакомое поле не отбрасывается, число не
|
- **Точка сохраняется дословно.** Незнакомое поле не отбрасывается, число не
|
||||||
округляется при записи. Нарушение — необратимая потеря: сырой архив живёт
|
округляется при записи. Нарушение — необратимая потеря: сырой архив живёт
|
||||||
@@ -70,12 +72,26 @@
|
|||||||
- Карта проекта для архитектурного прохода: `task review:context > tmp/review-context.md`
|
- Карта проекта для архитектурного прохода: `task review:context > tmp/review-context.md`
|
||||||
- Поднять изменение вживую: `task restart`, логи — `task logs`
|
- Поднять изменение вживую: `task restart`, логи — `task logs`
|
||||||
- Тесты и линт: `task test`, `task lint`
|
- Тесты и линт: `task test`, `task lint`
|
||||||
- Дорогое, вручную: `task verify:archive` (минута, живые данные)
|
- **Дорогое вне гейта, с адресатом:** `task verify:archive` (минута, живые
|
||||||
|
данные). **Кто и когда обязан:** пайплайн задачи — после любого изменения
|
||||||
|
разбора входного формата или правила слияния, до архивации change; вручную —
|
||||||
|
человек перед выкладкой. Не прогонялась — строка в границы покрытия, а не
|
||||||
|
молчание.
|
||||||
- **Запускать запрещено:** ничего, что пишет в `./data`, в рабочую БД и в боевой
|
- **Запускать запрещено:** ничего, что пишет в `./data`, в рабочую БД и в боевой
|
||||||
каталог архива. Замеры — только на копиях в `./tmp`.
|
каталог архива. Замеры — только на копиях в `./tmp`.
|
||||||
|
|
||||||
## Прод и поток
|
## Прод и поток
|
||||||
|
|
||||||
|
*Первая строка — главный вопрос эксплуатации этого проекта.*
|
||||||
|
|
||||||
|
> **Главный вопрос:** поток идёт непрерывно и молча, отправитель об отказе не
|
||||||
|
> узнает и не повторит — значит, дороже всего тихо потерянная доставка, а не
|
||||||
|
> упавший сервис.
|
||||||
|
|
||||||
|
> *(В сервисе, который сам опрашивает чужих демонов, первая строка была бы
|
||||||
|
> противоположной: «главный вопрос — что происходит, когда внешний сервис
|
||||||
|
> отвечает медленно, а не когда он упал».)*
|
||||||
|
|
||||||
- **Где:** один статический бинарь в контейнере на домашнем сервере, перед ним
|
- **Где:** один статический бинарь в контейнере на домашнем сервере, перед ним
|
||||||
обратный прокси с TLS, SQLite на диске. Ни оркестратора, ни реплик, ни дежурной
|
обратный прокси с TLS, SQLite на диске. Ни оркестратора, ни реплик, ни дежурной
|
||||||
смены.
|
смены.
|
||||||
@@ -84,11 +100,17 @@
|
|||||||
параллельной записью; приложение-источник на телефоне — молча перестаёт слать.
|
параллельной записью; приложение-источник на телефоне — молча перестаёт слать.
|
||||||
*(В другом проекте здесь были бы качалка, медиасервер, LLM и база метаданных, и
|
*(В другом проекте здесь были бы качалка, медиасервер, LLM и база метаданных, и
|
||||||
каждая — со своим «отвечает медленно», а не только «упала».)*
|
каждая — со своим «отвечает медленно», а не только «упала».)*
|
||||||
|
*(Если зависимостей нет — так и пишут: «внешних зависимостей нет, смотри на
|
||||||
|
диск и на СУБД». Пустой пункт называется пустым.)*
|
||||||
- **Кто заметит отказ:** один пользователь-владелец, в лучшем случае вечером, а
|
- **Кто заметит отказ:** один пользователь-владелец, в лучшем случае вечером, а
|
||||||
скорее не заметит вовсе.
|
скорее не заметит вовсе.
|
||||||
- **Характер потока:** телефон шлёт непрерывно и молча; обратной связи у
|
- **Характер потока:** телефон шлёт непрерывно и молча; обратной связи у
|
||||||
отправителя нет, об отказах он не сообщает, расписание плавает. Тихо
|
отправителя нет, об отказах он не сообщает, расписание плавает. Тихо
|
||||||
сломавшаяся доставка — главный эксплуатационный риск.
|
сломавшаяся доставка — главный эксплуатационный риск.
|
||||||
|
- **Представление данных и настройки хранилища:** запись — сжатый BLOB, читается
|
||||||
|
и пересобирается целиком на каждой операции (`internal/store`); журнал СУБД —
|
||||||
|
WAL; таймаут занятости — 5000 мс (`config.example.toml`); лимит тела приёма —
|
||||||
|
64 МБ; ретеншен сырого архива — 14 дней.
|
||||||
- **Числа (с провенансом):** нижний слой — порядка 135 тыс. точек в сутки
|
- **Числа (с провенансом):** нижний слой — порядка 135 тыс. точек в сутки
|
||||||
(замер, `docs/local-research.md`); тела доходили до 42 МБ (там же); запись —
|
(замер, `docs/local-research.md`); тела доходили до 42 МБ (там же); запись —
|
||||||
read-modify-write под конкурентными доставками (`docs/architecture.md`).
|
read-modify-write под конкурентными доставками (`docs/architecture.md`).
|
||||||
@@ -98,6 +120,19 @@
|
|||||||
|
|
||||||
## Модель угроз
|
## Модель угроз
|
||||||
|
|
||||||
|
*Первая строка — периметр.*
|
||||||
|
|
||||||
|
> **Периметр:** сервис открыт наружу через обратный прокси, недоверенным считается
|
||||||
|
> всё, что приходит по HTTP. Злоумышленник в локальной сети — вне периметра.
|
||||||
|
|
||||||
|
> *(У сервиса в доверенном контуре первая строка противоположна: «контур
|
||||||
|
> доверенный, публичного интернета здесь нет — не выдумывай его; недоверенное
|
||||||
|
> здесь — то, что отдают внешние демоны и трекеры».)*
|
||||||
|
|
||||||
|
> *(Контур ещё не развёрнут — тогда периметров два: «целевой — за прокси с TLS;
|
||||||
|
> сегодняшний — только локальная машина, токены пусты осознанно. **Находки
|
||||||
|
> строятся против целевого**, отсутствие TLS сегодня находкой не является».)*
|
||||||
|
|
||||||
- **Недоверенное:** тело доставки целиком (имена метрик, единицы, формы точек,
|
- **Недоверенное:** тело доставки целиком (имена метрик, единицы, формы точек,
|
||||||
метки времени, глубина вложенности, размер); заголовки доставки, часть которых
|
метки времени, глубина вложенности, размер); заголовки доставки, часть которых
|
||||||
участвует в решениях; содержимое архива внешнего экспорта (имена файлов внутри
|
участвует в решениях; содержимое архива внешнего экспорта (имена файлов внутри
|
||||||
@@ -117,13 +152,25 @@
|
|||||||
|
|
||||||
## Карта
|
## Карта
|
||||||
|
|
||||||
|
- **Основная ветка:** `master`. От неё берутся ветки задач, в неё вливается батч,
|
||||||
|
база диффа по умолчанию — `git merge-base HEAD master` (на самой ветке `HEAD~1`).
|
||||||
- Актуальные спеки: `openspec/specs/<capability>/spec.md`
|
- Актуальные спеки: `openspec/specs/<capability>/spec.md`
|
||||||
- Дельта-спеки изменения: `openspec/changes/<id>/specs/*/spec.md`
|
- Дельта-спеки изменения: `openspec/changes/<id>/specs/*/spec.md`
|
||||||
- Конвенции прозой: `docs/conventions.md`. Механизировано и потому **не
|
- **Нарезка capability и что из неё переехало в спеки:** режем по домену
|
||||||
проверяется проходом по конвенциям**: форма логов, `fmt.Print*`/`os.Getenv`/
|
(`ingest`, `storage`, `read-api`, `mcp`), а не по транспорту. В актуальные
|
||||||
`time.Now` мимо единых точек, сравнение ошибок, сторонние пакеты ошибок —
|
спеки перенесены `ingest` и `storage`; `read-api` описан только в
|
||||||
всё это правила в `.golangci.yml`.
|
`docs/architecture.md`, `mcp` — пока только в коде. Пробел в спеке по этим двум
|
||||||
|
темам — не находка, а известное состояние.
|
||||||
|
- Конвенции прозой: `docs/conventions.md` *(в другом проекте это каталог из
|
||||||
|
нескольких файлов — тогда перечисляют все:
|
||||||
|
`docs/conventions/{logging,errors,config,database,web-ui}.md`)*. Механизировано
|
||||||
|
и потому **не проверяется проходом по конвенциям**: форма логов,
|
||||||
|
`fmt.Print*`/`os.Getenv`/`time.Now` мимо единых точек, сравнение ошибок,
|
||||||
|
сторонние пакеты ошибок — всё это правила в `.golangci.yml`.
|
||||||
- Архитектура и решения: `docs/architecture.md`
|
- Архитектура и решения: `docs/architecture.md`
|
||||||
|
- **Наблюдения на живых данных:** `docs/local-research.md` — что реально шлёт
|
||||||
|
источник и чем это расходится с его документацией. *(Не ведём — так и пишут:
|
||||||
|
«наблюдений на живых данных не ведём».)*
|
||||||
- Журнал проскочивших дефектов: `docs/review-journal.md`
|
- Журнал проскочивших дефектов: `docs/review-journal.md`
|
||||||
- **Единые точки:** идентификаторы — `internal/ident`; время — `store.Now()`;
|
- **Единые точки:** идентификаторы — `internal/ident`; время — `store.Now()`;
|
||||||
разбор дат входного формата — один парсер в `internal/parse`; маппинг доменной
|
разбор дат входного формата — один парсер в `internal/parse`; маппинг доменной
|
||||||
@@ -159,6 +206,53 @@
|
|||||||
- **Клиент внешнего сервиса** — таймаут, протяжка `context`, поведение при
|
- **Клиент внешнего сервиса** — таймаут, протяжка `context`, поведение при
|
||||||
«медленно» против «упало», ретраи и их граница.
|
«медленно» против «упало», ретраи и их граница.
|
||||||
|
|
||||||
|
## Прецеденты
|
||||||
|
|
||||||
|
*Воспроизведённые случаи этого проекта: класс — симптом — чем воспроизведён —
|
||||||
|
чем закончилось. Прецедентов нет — так и пишут: «прецедентов не накоплено».*
|
||||||
|
|
||||||
|
- **Вырожденный ответ библиотеки, неотличимый от штатного.** Симптом: пересборка
|
||||||
|
докладывала «журнал разобран целиком», а часть записей не доезжала. Причина:
|
||||||
|
контрольная точка журнала СУБД под занятой блокировкой возвращала `-1` вместо
|
||||||
|
пары чисел, и сравнение `-1 >= -1` читалось как успех — 1492 тика из 5502.
|
||||||
|
Воспроизведено экспериментом на стенде (`tmp/probe-checkpoint/`), из
|
||||||
|
документации драйвера не следовало. Закончилось: явная проверка вырожденного
|
||||||
|
значения + вопрос 8 в эксплуатационном проходе.
|
||||||
|
- **Канонизация внутри транзакции.** Симптом: соседняя доставка получала «база
|
||||||
|
занята». Причина: пересборка держала блокировку записи 5.019 с при таймауте
|
||||||
|
занятости 5000 мс — канонизация и хеширование шли внутри транзакции.
|
||||||
|
Воспроизведено замером на копии БД. Закончилось: вынос канонизации из
|
||||||
|
транзакции; числа — в раздел `## Прод и поток`.
|
||||||
|
- **Пик памяти на распаковке.** Симптом: контейнер убивался по памяти на крупных
|
||||||
|
доставках. Причина: сжатая запись распаковывалась целиком, пик 768 МиБ на теле
|
||||||
|
40 МБ. Воспроизведено прогоном на реальном пакете из `testdata`. Закончилось:
|
||||||
|
потоковая обработка; факт «запись — сжатый BLOB» вынесен в бриф, потому что без
|
||||||
|
него замер не читается как аномалия.
|
||||||
|
|
||||||
|
## Типовые ложноположительные
|
||||||
|
|
||||||
|
*Находки, которые здесь выглядят убедительно и всегда неверны. Пусто — так и
|
||||||
|
пишут.*
|
||||||
|
|
||||||
|
- «Значения из входа надо нормализовать перед записью» — инвариант требует
|
||||||
|
дословного хранения; нормализация тут порча, а не улучшение.
|
||||||
|
- «Приём должен отвечать ошибкой на непонятое содержимое» — инвариант «сохранили
|
||||||
|
— значит приняли»; отправитель доставку не повторит.
|
||||||
|
- «Порядок ключей в JSON стабилен, канонизация избыточна» — наблюдение на живых
|
||||||
|
данных говорит обратное.
|
||||||
|
- «Вынести в конфиг» про значения, заданные внешним форматом.
|
||||||
|
|
||||||
|
## Вопросы к проходам
|
||||||
|
|
||||||
|
*Производные от журнала: вопрос конкретному проходу плюс ссылка на запись, из
|
||||||
|
которой он взялся. Пусто — так и пишут.*
|
||||||
|
|
||||||
|
- `ops`: что произойдёт при откате бинаря поверх уже накатившейся миграции —
|
||||||
|
стартует ли старая версия молча (журнал, запись 2026-05-12).
|
||||||
|
- `adversary`: имена файлов внутри архива внешнего экспорта мы не формировали —
|
||||||
|
проверь путь от имени в архиве до операции с файловой системой (журнал, запись
|
||||||
|
2026-06-03).
|
||||||
|
|
||||||
## Триггеры
|
## Триггеры
|
||||||
|
|
||||||
- `deep`: миграция в `internal/store/migrations/`, новый пакет `internal/*`,
|
- `deep`: миграция в `internal/store/migrations/`, новый пакет `internal/*`,
|
||||||
@@ -170,6 +264,10 @@
|
|||||||
|
|
||||||
## Недоступно проверке
|
## Недоступно проверке
|
||||||
|
|
||||||
|
### Не проверит ни один проход
|
||||||
|
|
||||||
|
*Принципиальные границы. По факту промаха не пересматриваются.*
|
||||||
|
|
||||||
- Поведение внешнего приложения-источника на следующем его обновлении.
|
- Поведение внешнего приложения-источника на следующем его обновлении.
|
||||||
- Что реально лежит в системе-источнике: сверить можно только ручным экспортом,
|
- Что реально лежит в системе-источнике: сверить можно только ручным экспортом,
|
||||||
а он делается раз в 2–3 месяца.
|
а он делается раз в 2–3 месяца.
|
||||||
@@ -177,3 +275,15 @@
|
|||||||
нагрузки.
|
нагрузки.
|
||||||
- Завязка внешних потребителей на текущую форму ответа.
|
- Завязка внешних потребителей на текущую форму ответа.
|
||||||
- Суждение «этой функциональности не должно существовать».
|
- Суждение «этой функциональности не должно существовать».
|
||||||
|
|
||||||
|
### Перестали проверять сознательно
|
||||||
|
|
||||||
|
*Что, когда, почему и где записано. Пересматривается первым, как только что-то
|
||||||
|
проскочило. Пусто — так и пишут: «сознательно ничего не отключали».*
|
||||||
|
|
||||||
|
- **Поимённая сверка со стайлгайдами языка** — с 2026-05, вместе с упразднением
|
||||||
|
прохода про идиоматичность (журнал ревью, запись 2026-05-04). Класс обратимый:
|
||||||
|
портит форму кода, не данные.
|
||||||
|
- **Правило линтера про длину функции** — снято 2026-06-18: ложных срабатываний
|
||||||
|
больше трети (журнал, там же). Вернуть, если проскочит дефект «функция делает
|
||||||
|
три вещи».
|
||||||
|
|||||||
@@ -18,21 +18,28 @@
|
|||||||
передавать каждому проходу;
|
передавать каждому проходу;
|
||||||
2. `docs/review-brief.md`;
|
2. `docs/review-brief.md`;
|
||||||
3. `.claude/review-brief.md`;
|
3. `.claude/review-brief.md`;
|
||||||
4. брифа нет — **деградированный режим** (см. ниже).
|
4. брифа нет ни по одному пути — **он заводится**, скиллом
|
||||||
|
`av-dev-pipeline:project-brief`, и прогон продолжается по заведённому.
|
||||||
|
|
||||||
Разрешает путь конвейер, один раз, и дальше передаёт готовым. Агент, получивший
|
Разрешает путь конвейер, один раз, и дальше передаёт готовым. Агент, получивший
|
||||||
путь в задании, сам ничего не ищет.
|
путь в задании, сам ничего не ищет.
|
||||||
|
|
||||||
## Деградированный режим
|
## Деградированный режим — исход, а не умолчание
|
||||||
|
|
||||||
Брифа нет — проходы работают, но их recall падает предсказуемым образом, и это
|
Он включается ровно тогда, когда бриф **завести не удалось**: репозиторий
|
||||||
**обязано быть названо**, а не сглажено. Каждый проход без брифа:
|
доступен только на чтение, человек прямо запретил, инварианты вывести неоткуда.
|
||||||
|
Во всех остальных случаях брифа быть обязано.
|
||||||
|
|
||||||
|
Каждый проход в этом режиме:
|
||||||
|
|
||||||
- не присваивает `critical` по основанию «нарушен инвариант проекта» — инвариантов
|
- не присваивает `critical` по основанию «нарушен инвариант проекта» — инвариантов
|
||||||
он не знает;
|
он не знает;
|
||||||
- не оперирует числами объёма и потока — формулирует условиями;
|
- не оперирует числами объёма и потока — формулирует условиями;
|
||||||
- пишет в границы покрытия строку: «брифа проекта нет: инварианты, модель угроз и
|
- пишет в границы покрытия строку: «брифа проекта нет (<причина>): инварианты,
|
||||||
профиль нагрузки неизвестны; находки этих классов не искались».
|
модель угроз и профиль нагрузки неизвестны; находки этих классов не искались».
|
||||||
|
|
||||||
|
Причина обязательна: без неё строка неотличима от «мы просто не стали», и
|
||||||
|
одинаковая строка в каждом отчёте перестаёт читаться на третьей задаче.
|
||||||
|
|
||||||
Триаж сводит эти строки в одну и выносит в финальный отчёт. Отсутствие брифа —
|
Триаж сводит эти строки в одну и выносит в финальный отчёт. Отсутствие брифа —
|
||||||
дыра покрытия, а не нейтральное умолчание.
|
дыра покрытия, а не нейтральное умолчание.
|
||||||
@@ -67,6 +74,14 @@ Markdown. Разделы — заголовки второго уровня с *
|
|||||||
Это единственный раздел, который **цитируется формулировкой**, а не пересказывается
|
Это единственный раздел, который **цитируется формулировкой**, а не пересказывается
|
||||||
ссылкой: по нему присваивается severity, и пересказ здесь стоит неверной оценки.
|
ссылкой: по нему присваивается severity, и пересказ здесь стоит неверной оценки.
|
||||||
|
|
||||||
|
**Оговорка про severity, потому что она единственная не цитируется.** Проекты
|
||||||
|
почти никогда не пишут severity рядом с инвариантом — её приходится выводить, и
|
||||||
|
правило вывода одно: **по обратимости последствия**. Необратимо и молча —
|
||||||
|
`critical`; лечится повтором, видно сразу — ниже. Выведенная severity помечается
|
||||||
|
словом «выведена по обратимости», а не выдаётся за решение проекта: проход ставит
|
||||||
|
по ней `critical`, и он вправе знать, чьё это суждение. Лучший исход — дописать
|
||||||
|
severity туда, откуда цитируется формулировка, и тогда пометка снимается.
|
||||||
|
|
||||||
Читают: `specs` (режим 1 — отражены ли задетые инварианты в спеке), `code`,
|
Читают: `specs` (режим 1 — отражены ли задетые инварианты в спеке), `code`,
|
||||||
`adversary`, `architecture`, `triage` (ранжирование и разметка «развилка»).
|
`adversary`, `architecture`, `triage` (ранжирование и разметка «развилка»).
|
||||||
|
|
||||||
@@ -96,6 +111,14 @@ Markdown. Разделы — заголовки второго уровня с *
|
|||||||
- **запуск изменения вживую** — чем поднять и как проверить поведение (нужно
|
- **запуск изменения вживую** — чем поднять и как проверить поведение (нужно
|
||||||
пайплайну задачи на шаге поведенческой верификации);
|
пайплайну задачи на шаге поведенческой верификации);
|
||||||
- **тесты, линт, дополнительные проверки** — и какие из них дорогие;
|
- **тесты, линт, дополнительные проверки** — и какие из них дорогие;
|
||||||
|
- **дорогие проверки вне гейта — с адресатом.** Мало сказать «`verify:archive`
|
||||||
|
идёт минуту»: назови, **кто и когда обязан** её гонять — какой класс изменения
|
||||||
|
её требует, кто её запускает (проход, пайплайн, человек) и что делать, если она
|
||||||
|
не прогонялась. Без адресата дорогая проверка не гоняется никогда, а её
|
||||||
|
краснота не видна никому. **Адресат в проекте не записан нигде — тогда бриф его
|
||||||
|
назначает**, и назначение помечается: «адресат назначен брифом, владельцем не
|
||||||
|
подтверждён». Это тот же класс, что выведенная severity у инварианта: слот
|
||||||
|
честнее заполнить назначением с пометкой, чем оставить пустым;
|
||||||
- **что запускать запрещено**: рабочая БД, боевой каталог данных, внешние
|
- **что запускать запрещено**: рабочая БД, боевой каталог данных, внешние
|
||||||
сервисы. Формулируй запретом с путями, а не «будь осторожен».
|
сервисы. Формулируй запретом с путями, а не «будь осторожен».
|
||||||
|
|
||||||
@@ -103,18 +126,48 @@ Markdown. Разделы — заголовки второго уровня с *
|
|||||||
|
|
||||||
### `## Прод и поток` — обязателен
|
### `## Прод и поток` — обязателен
|
||||||
|
|
||||||
Материал для эксплуатационного прохода, и он же — половина ранжирования триажа:
|
Материал для эксплуатационного прохода, и он же — половина ранжирования триажа.
|
||||||
|
|
||||||
|
**Первой строкой — главный вопрос эксплуатации этого проекта.** Один заголовок
|
||||||
|
покрывает противоположные постановки: «поток идёт непрерывно и молча, отправитель
|
||||||
|
об отказе не узнает» и «мы опрашиваем чужие сервисы, и главный вопрос — что
|
||||||
|
делать, когда сосед отвечает медленно». От того, какая из них здесь главная,
|
||||||
|
зависит порядок находок в отчёте, а вывести её проход не может — он видит
|
||||||
|
одинаковый код.
|
||||||
|
|
||||||
|
Дальше:
|
||||||
|
|
||||||
- где это работает: машина, окружение, что рядом, кто перезапускает;
|
- где это работает: машина, окружение, что рядом, кто перезапускает;
|
||||||
- **внешние зависимости поимённо** и чем каждая отказывает: не только «падает», но
|
- **внешние зависимости поимённо** и чем каждая отказывает: не только «падает», но
|
||||||
и «отвечает медленно», «молчит», «отдаёт мусор». Эксплуатационный проход
|
и «отвечает медленно», «молчит», «отдаёт мусор». Эксплуатационный проход
|
||||||
спрашивает про каждую отдельно, и список зависимостей он взять больше неоткуда;
|
спрашивает про каждую отдельно, и список зависимостей он взять больше неоткуда.
|
||||||
|
**Зависимостей почти нет — так и напиши**: «внешних зависимостей нет, смотри на
|
||||||
|
диск и на СУБД». Пустой пункт, не названный пустым, проход тратит впустую или
|
||||||
|
заполняет выдумкой;
|
||||||
- **кто заметит отказ и когда** — есть ли вообще наблюдатель;
|
- **кто заметит отказ и когда** — есть ли вообще наблюдатель;
|
||||||
- **характер потока**: непрерывный и молчаливый, по запросу, по расписанию; есть
|
- **характер потока**: непрерывный и молчаливый, по запросу, по расписанию; есть
|
||||||
ли обратная связь у отправителя;
|
ли обратная связь у отправителя;
|
||||||
|
- **представление данных и настройки хранилища.** Чем физически лежит запись
|
||||||
|
(сжатый BLOB, JSON-строка, колонки), что происходит при чтении и записи
|
||||||
|
(распаковка целиком, read-modify-write), и **настройки, у которых есть
|
||||||
|
числовое значение**: таймаут занятости СУБД, режим журналирования, лимит тела,
|
||||||
|
размер пула, ретеншен. Это не украшение раздела: ровно эти два факта
|
||||||
|
превращают **замер** в находку. Замеренный пик памяти — аномалия только если
|
||||||
|
известно, что запись лежит сжатой и распаковывается целиком; замеренная
|
||||||
|
длительность удержания блокировки — гарантированный отказ соседа только если
|
||||||
|
известно, чему равен таймаут занятости. Без этих фактов проход снимет верное
|
||||||
|
число и честно понизит находку до гипотезы, потому что сравнить его будет не с
|
||||||
|
чем. Цена пропущенного пункта здесь не «не найдём», а **«найдём и не
|
||||||
|
починим»**. Числа с провенансом — в следующем пункте, воспроизведённые случаи —
|
||||||
|
в разделе `## Прецеденты`;
|
||||||
- **измеренные числа с провенансом**: объёмы, размеры тел, темп, размеры таблиц.
|
- **измеренные числа с провенансом**: объёмы, размеры тел, темп, размеры таблиц.
|
||||||
Число без источника проход обязан превратить в условие — так и напиши, откуда
|
Число без источника проход обязан превратить в условие — так и напиши, откуда
|
||||||
оно;
|
оно. **Замер и настройка — разные пункты, и путать их нельзя:** настройка
|
||||||
|
(`busy_timeout`, лимит тела, размер пула) живёт пунктом выше и говорит, чему
|
||||||
|
равен порог; замер говорит, что происходит на самом деле. Проекту без
|
||||||
|
наблюдаемой нагрузки нечего писать во втором пункте — **так и напиши**:
|
||||||
|
«измеренных чисел нагрузки нет, всё, что ниже, — настройки». Тогда проход
|
||||||
|
формулирует условиями осознанно, а не потому, что не нашёл;
|
||||||
- **что обратимо, а что нет.** Падение, которое лечится повтором, и тихая потеря,
|
- **что обратимо, а что нет.** Падение, которое лечится повтором, и тихая потеря,
|
||||||
которую нечем восстановить, — разные классы, и порядок находок в отчёте зависит
|
которую нечем восстановить, — разные классы, и порядок находок в отчёте зависит
|
||||||
от того, какой из них здесь главный.
|
от того, какой из них здесь главный.
|
||||||
@@ -123,6 +176,21 @@ Markdown. Разделы — заголовки второго уровня с *
|
|||||||
|
|
||||||
### `## Модель угроз` — обязателен
|
### `## Модель угроз` — обязателен
|
||||||
|
|
||||||
|
**Первой строкой — периметр.** «Сервис открыт наружу; злоумышленник в локальной
|
||||||
|
сети неинтересен» и «контур доверенный, публичного интернета здесь нет, не
|
||||||
|
выдумывай его» — это один и тот же заголовок при противоположной постановке, и
|
||||||
|
враждебный проход не может выбрать между ними сам. Периметр, объявленный первой
|
||||||
|
строкой, задаёт смысл всему остальному разделу.
|
||||||
|
|
||||||
|
**Периметров может быть два — целевой и сегодняшний**, если контур ещё не
|
||||||
|
развёрнут: «целевой — открыт наружу за прокси с TLS; сегодняшний — только
|
||||||
|
локальная машина, токены пусты осознанно». Тогда назови оба и скажи прямо,
|
||||||
|
**против какого строятся находки**. Иначе враждебный проход либо завалит отчёт
|
||||||
|
находками «нет TLS» по сегодняшнему состоянию, либо не станет искать дефекты,
|
||||||
|
спящие до выкладки, — оба исхода стоят прохода целиком.
|
||||||
|
|
||||||
|
Дальше:
|
||||||
|
|
||||||
- **что недоверенное** и каким каналом приходит: тело запроса, файл, аргумент
|
- **что недоверенное** и каким каналом приходит: тело запроса, файл, аргумент
|
||||||
команды, ответ внешней системы, содержимое архива;
|
команды, ответ внешней системы, содержимое архива;
|
||||||
- **из чего строятся пути и ключи** — раскладка файлов на диске, состав
|
- **из чего строятся пути и ключи** — раскладка файлов на диске, состав
|
||||||
@@ -139,9 +207,32 @@ Markdown. Разделы — заголовки второго уровня с *
|
|||||||
|
|
||||||
Где что лежит, путями:
|
Где что лежит, путями:
|
||||||
|
|
||||||
|
- **основная ветка** — её имя. Отсюда берутся ветки задач, в неё вливается батч,
|
||||||
|
от неё считается база диффа по умолчанию (`git merge-base HEAD <основная>`).
|
||||||
|
Батч подставляет это имя в каждую команду git; взять его больше неоткуда, а
|
||||||
|
угадывание между `master` и `main` ломает интеграцию целиком;
|
||||||
- актуальные спеки и дельта-спеки предлагаемого изменения;
|
- актуальные спеки и дельта-спеки предлагаемого изменения;
|
||||||
- конвенции прозой — и **какая их часть уже механизирована** правилом (её проход
|
- **нарезка capability и миграционное состояние спек** — по какому признаку
|
||||||
по конвенциям не проверяет);
|
проект режет capability (по домену, по транспорту, по подсистеме), какие из них
|
||||||
|
уже перенесены в актуальные спеки, а какие ещё живут только в документации или
|
||||||
|
в коде. Проход по спекам иначе примет непереехавшую тему за пробел в спеке, а
|
||||||
|
архитектурный — за отсутствие понятия;
|
||||||
|
- конвенции прозой — **файл или каталог файлов**, путями; и **какая их часть уже
|
||||||
|
механизирована** правилом. Механизация бывает **в нескольких местах сразу**:
|
||||||
|
конфиг линтера, собственный анализатор и — чаще всего незамеченное —
|
||||||
|
**тест-сканер исходников** (правило про направление зависимостей, форму
|
||||||
|
миграций, логику в транспорте), который внешне неотличим от обычного теста.
|
||||||
|
Перечисли все места: непойманное место механизации означает, что проход по
|
||||||
|
конвенциям будет добросовестно проверять уже проверенное;
|
||||||
|
- **наблюдения на живых данных** — где записано, как внешний мир ведёт себя на
|
||||||
|
самом деле (что реально шлёт источник, чем документация формата расходится с
|
||||||
|
практикой, какие числа сняты с живого потока). Их спрашивают `specs`, `reimpl`
|
||||||
|
и `ops`, и все трое — «из раздела `## Карта`». Отдельного файла нет — **так и
|
||||||
|
напиши**, и перечисли суррогаты: спеки, где наблюдения рассыпаны, комментарии в
|
||||||
|
адаптерах, `testdata`. Отдельный файл — лучшая форма, потому что при нескольких
|
||||||
|
внешних источниках наблюдения иначе не сойдутся в одном месте; но честный
|
||||||
|
перечень суррогатов лучше молчания, от которого три прохода ищут
|
||||||
|
несуществующий путь;
|
||||||
- архитектура и решения; журнал проскочивших дефектов;
|
- архитектура и решения; журнал проскочивших дефектов;
|
||||||
- **единые точки проекта** — где генерируются идентификаторы и время, где
|
- **единые точки проекта** — где генерируются идентификаторы и время, где
|
||||||
единственный парсер входного формата, где маппинг доменной ошибки в код ответа,
|
единственный парсер входного формата, где маппинг доменной ошибки в код ответа,
|
||||||
@@ -161,10 +252,83 @@ Markdown. Разделы — заголовки второго уровня с *
|
|||||||
репозиторий, воркер, клиент внешнего API, CLI-команда, файловое хранилище), и по
|
репозиторий, воркер, клиент внешнего API, CLI-команда, файловое хранилище), и по
|
||||||
3–5 **специфичных для рода** проверяемых свойств к каждому.
|
3–5 **специфичных для рода** проверяемых свойств к каждому.
|
||||||
|
|
||||||
|
**Рода, а не инвентарь того, что сейчас лежит в пакетах.** Список пишется по
|
||||||
|
природе проекта: род, который проект уже задумал, но ещё не написал, включать
|
||||||
|
полезно (рубрика на него понадобится ровно на той задаче, где его заводят); а
|
||||||
|
род, случайно оказавшийся в коде в одном экземпляре, — нет. Иначе раздел
|
||||||
|
протухает на каждой задаче и требует пересмотра, которого никто не делает.
|
||||||
|
|
||||||
Читает: `rubric`. Без раздела рубрика выродится в общие слова и повторит
|
Читает: `rubric`. Без раздела рубрика выродится в общие слова и повторит
|
||||||
конвенции — то есть станет applicative-проходом, ради отсутствия которого она и
|
конвенции — то есть станет applicative-проходом, ради отсутствия которого она и
|
||||||
существует.
|
существует.
|
||||||
|
|
||||||
|
### `## Прецеденты` — обязателен, хотя бы строкой «пусто»
|
||||||
|
|
||||||
|
**Воспроизведённые случаи этого проекта, с оракулом.** Не «здесь бывают гонки», а
|
||||||
|
«такой дефект здесь уже был, вот чем он воспроизведён»: что оказалось не так,
|
||||||
|
каким экспериментом или тестом это показано, какими числами, где это записано.
|
||||||
|
|
||||||
|
Каждый пункт — четыре вещи:
|
||||||
|
|
||||||
|
- **класс дефекта** — так, чтобы проход узнал его в другом месте;
|
||||||
|
- **как проявился** — симптом, который увидел человек;
|
||||||
|
- **чем воспроизведён** — команда, тест, стенд, замер. Без этого пункт
|
||||||
|
превращается в байку. **Регрессионный тест, написанный вместе с починкой,
|
||||||
|
годится** наравне с независимым экспериментом: он исполняемый и падает на
|
||||||
|
старом коде, а это всё, что требуется от оракула. Слабее он ровно в одном —
|
||||||
|
сформулирован уже зная ответ; это отмечается словом, а не служит поводом
|
||||||
|
выбросить пункт;
|
||||||
|
- **чем закончилось** — починка, правило линтера, пункт брифа, «ничего».
|
||||||
|
|
||||||
|
Зачем раздел существует. Прецедент — самая сильная опора, какая у прохода вообще
|
||||||
|
бывает: он проектный, воспроизводимый и уже однажды оказался правдой. Пока слота
|
||||||
|
не было, прецеденты вмерзали в charter'ы проходов — то есть каждый проект читал
|
||||||
|
про чужую контрольную точку в чужой СУБД и искал её у себя. Charter описывает
|
||||||
|
**форму класса**, бриф — **случай**.
|
||||||
|
|
||||||
|
Источники: журнал проскочивших дефектов, архивные отчёты триажа, `git log` по
|
||||||
|
починкам. Прецедентов нет — так и напиши: «прецедентов не накоплено», и это
|
||||||
|
честнее пустого раздела.
|
||||||
|
|
||||||
|
Читают: все проходы — свой класс; `triage` — как готовый оракул.
|
||||||
|
|
||||||
|
### `## Типовые ложноположительные` — необязателен, но без него отсев слепой
|
||||||
|
|
||||||
|
Находки, которые в **этом** проекте выглядят убедительно и всегда неверны. Это
|
||||||
|
единственный проектный вход в шаг триажа «отсев вкусовщины»: общие критерии
|
||||||
|
(«не меняет поведения, не влияет на стоимость следующего изменения, не нарушает
|
||||||
|
записанного») ловят вкусовщину, но не ловят находку, которая нарушает общее
|
||||||
|
правило **осознанно**.
|
||||||
|
|
||||||
|
Каждый пункт — формулировка находки, какой её выдаёт проход, плюс одна строка
|
||||||
|
«почему здесь это не дефект». Типичные обитатели: «дословное хранение надо
|
||||||
|
нормализовать» там, где дословность — инвариант; «повтор надо сделать
|
||||||
|
идемпотентным» там, где повтор невозможен по построению; «это надо вынести в
|
||||||
|
конфиг» там, где значение задано внешним протоколом.
|
||||||
|
|
||||||
|
Читает: `triage`.
|
||||||
|
|
||||||
|
### `## Вопросы к проходам` — необязателен
|
||||||
|
|
||||||
|
Проектные вопросы, адресованные **поимённо** конкретному проходу. Главный их
|
||||||
|
источник — журнал проскочивших дефектов: запись «проход не мог этого знать» чаще
|
||||||
|
всего лечится фактом в другом разделе, но иногда лечится не фактом, а
|
||||||
|
**вопросом**: «`ops`, спроси про поведение при откате бинаря поверх новой схемы»,
|
||||||
|
«`adversary`, проверь имена внутри архива». Такие вопросы живут здесь, а не в
|
||||||
|
charter'е: charter общий для всех проектов, а вопрос выведен из промаха в этом.
|
||||||
|
|
||||||
|
**Журнал — не единственный источник, а лучший.** У молодого проекта журнал пуст,
|
||||||
|
и слот тогда заполняется из того, что есть: незакрытые находки аудита, известное
|
||||||
|
расхождение кода с документацией, место, где решение принято «пока так». Правило
|
||||||
|
одно и не смягчается — **у каждого вопроса указан провенанс**, и по нему видно,
|
||||||
|
насколько он выстрадан: «журнал, запись такая-то» весит больше, чем «открытая
|
||||||
|
находка аудита».
|
||||||
|
|
||||||
|
Форма: `<имя прохода>: <вопрос> (<провенанс>)`. Проход, увидев свой блок, задаёт
|
||||||
|
эти вопросы **дополнительно** к обязательным — и отвечает на них в выводе явно.
|
||||||
|
|
||||||
|
Читают: проходы, названные поимённо.
|
||||||
|
|
||||||
### `## Триггеры` — необязателен
|
### `## Триггеры` — необязателен
|
||||||
|
|
||||||
Проектная конкретизация правила выбора профиля: какие пути и контракты означают
|
Проектная конкретизация правила выбора профиля: какие пути и контракты означают
|
||||||
@@ -175,16 +339,35 @@ Markdown. Разделы — заголовки второго уровня с *
|
|||||||
|
|
||||||
Читают: скилл конвейера, пайплайн задачи.
|
Читают: скилл конвейера, пайплайн задачи.
|
||||||
|
|
||||||
### `## Недоступно проверке` — обязателен
|
### `## Недоступно проверке` — обязателен, и делится на два подраздела
|
||||||
|
|
||||||
Что не проверит ни один проход и почему: поведение внешних систем и их будущих
|
Раздел целиком уезжает в границы покрытия финального отчёта — он существует ровно
|
||||||
версий, реальный профиль нагрузки, соответствие сохранённого действительности,
|
затем, чтобы «критичных проблем не обнаружено» никогда не читалось как «проверено
|
||||||
завязка внешних потребителей на текущую форму, суждение «а нужна ли эта
|
всё». Но внутри лежат **два разных класса**, и смешивать их нельзя: при следующем
|
||||||
функциональность».
|
промахе один пересматривается, другой нет.
|
||||||
|
|
||||||
Этот раздел целиком уезжает в границы покрытия финального отчёта. Он существует
|
#### `### Не проверит ни один проход`
|
||||||
ровно затем, чтобы «критичных проблем не обнаружено» никогда не читалось как
|
|
||||||
«проверено всё».
|
Принципиально недоступное: поведение внешних систем и их будущих версий, реальный
|
||||||
|
профиль нагрузки, соответствие сохранённого действительности, завязка внешних
|
||||||
|
потребителей на текущую форму, суждение «а нужна ли эта функциональность».
|
||||||
|
|
||||||
|
Этот список не пересматривается по факту промаха: дефект отсюда — не ошибка
|
||||||
|
конвейера, а его честная граница. Он меняется только когда меняется сам проект
|
||||||
|
(появился стенд, появился второй потребитель, появилась телеметрия).
|
||||||
|
|
||||||
|
#### `### Перестали проверять сознательно`
|
||||||
|
|
||||||
|
Решения о сужении: перестали звать проход, понизили профиль правилом, сузили
|
||||||
|
класс проверяемого, сняли правило линтера как шумное. Каждый пункт — **что
|
||||||
|
перестали, когда и почему**, со ссылкой на запись журнала ревью.
|
||||||
|
|
||||||
|
Этот список **пересматривается первым**, как только что-то проскочило: первый
|
||||||
|
вопрос по любому пропущенному дефекту — «не тот ли это класс, который мы перестали
|
||||||
|
проверять». Пункт, из-за которого дефект проскочил, либо возвращается, либо
|
||||||
|
получает строку «оставляем, цена поимки выше цены дефекта» с датой.
|
||||||
|
|
||||||
|
Оба подраздела обязательны; пустой называется пустым.
|
||||||
|
|
||||||
Читает: `triage`; каждый проход — свою часть.
|
Читает: `triage`; каждый проход — свою часть.
|
||||||
|
|
||||||
@@ -194,15 +377,37 @@ Markdown. Разделы — заголовки второго уровня с *
|
|||||||
или в архитектуре, попадает сюда ссылкой и одной строкой сути. Два дома для
|
или в архитектуре, попадает сюда ссылкой и одной строкой сути. Два дома для
|
||||||
одного факта разъезжаются, и разошедшийся бриф хуже отсутствующего: он выглядит
|
одного факта разъезжаются, и разошедшийся бриф хуже отсутствующего: он выглядит
|
||||||
актуальным. Исключение одно — раздел инвариантов, он цитируется.
|
актуальным. Исключение одно — раздел инвариантов, он цитируется.
|
||||||
- **Числа — с провенансом.** «Тела доходили до 42 МБ (замер, ссылка)». Число без
|
- **Числа — с провенансом, и провенанс проверяется переходом по ссылке.** «Тела
|
||||||
источника проход не имеет права использовать как утверждение.
|
доходили до 42 МБ (замер, ссылка)». Число без источника проход не имеет права
|
||||||
|
использовать как утверждение. Отдельный и более коварный случай — **число, чей
|
||||||
|
источник по ссылке не подтверждается**: в документе по ссылке другое число, или
|
||||||
|
его там нет вовсе. Такое число не выбрасывается и не переписывается по догадке:
|
||||||
|
оно остаётся с пометкой «расходится с источником: там <что нашли>», а проход
|
||||||
|
обязан читать его как условие, а не как замер. Молча подставить «правильное»
|
||||||
|
число хуже всего — расхождение перестанет быть видно, а причина его останется.
|
||||||
|
- **Пустой пункт называется пустым.** «Внешних зависимостей нет — смотри на диск
|
||||||
|
и на СУБД», «прецедентов не накоплено», «наблюдений на живых данных не ведём»,
|
||||||
|
«измеренных чисел нагрузки нет», «сознательно ничего не отключали». Отсутствие
|
||||||
|
строки читается проходом как «здесь не написали», и он тратит обязательный
|
||||||
|
вопрос впустую либо заполняет пробел выдумкой. Прямое «пусто» стоит одной
|
||||||
|
строки и экономит проход целиком.
|
||||||
|
- **Назначенное помечается назначенным.** Бриф отражает решения проекта, но
|
||||||
|
местами оказывается **первым** местом, где решение вообще записано: severity у
|
||||||
|
инварианта, адресат дорогой проверки, периметр, восстановленный из конфига.
|
||||||
|
Так можно — молчать хуже, — но пометка обязательна («выведена по обратимости»,
|
||||||
|
«назначен брифом, владельцем не подтверждён»). Проход имеет право знать, чьё
|
||||||
|
это суждение, а владелец — увидеть, что за него что-то решили.
|
||||||
- **Что вне модели — называется явно.** Это относится и к угрозам, и к нагрузке,
|
- **Что вне модели — называется явно.** Это относится и к угрозам, и к нагрузке,
|
||||||
и к классам находок, которые проект сознательно перестал проверять.
|
и к классам находок, которые проект сознательно перестал проверять (последние —
|
||||||
|
в свой подраздел `## Недоступно проверке`, а не вперемешку с принципиальным).
|
||||||
- **Бриф подчиняется промоуту.** Свойство, ставшее правилом линтера, из брифа
|
- **Бриф подчиняется промоуту.** Свойство, ставшее правилом линтера, из брифа
|
||||||
вычёркивается — как и из конвенций, и из charter'ов (см.
|
вычёркивается — как и из конвенций, и из charter'ов (см.
|
||||||
[promote.md](promote.md), шаг 3).
|
[promote.md](promote.md), шаг 3).
|
||||||
- **Когда обновлять:** сменился гейт; появился новый контур, зависимость или
|
- **Когда обновлять:** сменился гейт; появился новый контур, зависимость или
|
||||||
источник входа; журнал ревью получил запись вида «проход не мог этого знать».
|
источник входа; журнал ревью получил запись вида «проход не мог этого знать»;
|
||||||
Планового пересмотра нет.
|
воспроизвели дефект — он идёт в `## Прецеденты`. Планового пересмотра нет.
|
||||||
- **Бриф ведёт проект**, а не плагин. Плагин его только читает и никогда не
|
- **Заводится и обновляется шагом, а не руками** — скиллом
|
||||||
правит.
|
`av-dev-pipeline:project-brief`. Он же вызывается автоматически, когда конвейер
|
||||||
|
или пайплайн задачи не нашли брифа ни по одному пути.
|
||||||
|
- **Бриф ведёт проект**, а не плагин. Файл живёт в репозитории проекта; плагин
|
||||||
|
его читает и заводит по шаблону, но не хранит у себя и не подменяет.
|
||||||
|
|||||||
@@ -23,7 +23,8 @@
|
|||||||
калибровки: проход, чьи находки регулярно доезжают до конвенции, оправдан;
|
калибровки: проход, чьи находки регулярно доезжают до конвенции, оправдан;
|
||||||
проход, чьи находки не доезжают никогда, — кандидат на `drop` (см.
|
проход, чьи находки не доезжают никогда, — кандидат на `drop` (см.
|
||||||
[calibration.md](calibration.md)).
|
[calibration.md](calibration.md)).
|
||||||
- Место записи — файл конвенций проекта (путь — в разделе `## Карта` брифа). Если
|
- Место записи — конвенции проекта, файл или нужный файл каталога (путь — в
|
||||||
|
разделе `## Карта` брифа). Если
|
||||||
тема относится к поведению системы, а не к тому, как мы пишем код, — это не
|
тема относится к поведению системы, а не к тому, как мы пишем код, — это не
|
||||||
конвенция, а требование: заводится дельта-спека обычным путём.
|
конвенция, а требование: заводится дельта-спека обычным путём.
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,11 @@
|
|||||||
а потому, что здесь лежит цена: если что-то теперь проскочит, первый вопрос —
|
а потому, что здесь лежит цена: если что-то теперь проскочит, первый вопрос —
|
||||||
«не тот ли это класс, который мы перестали проверять».
|
«не тот ли это класс, который мы перестали проверять».
|
||||||
|
|
||||||
|
Каждое такое решение обязано получить **строку в брифе** — в подразделе
|
||||||
|
`### Перестали проверять сознательно` раздела `## Недоступно проверке`. Журнал
|
||||||
|
хранит «почему тогда так решили», бриф — то, во что смотрит каждый прогон.
|
||||||
|
Решение, оставшееся только в журнале, в границы покрытия не доедет.
|
||||||
|
|
||||||
## Форма записи
|
## Форма записи
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -41,7 +46,11 @@
|
|||||||
|
|
||||||
- **в бриф проекта** — если проход не мог знать факта: объём, характер потока,
|
- **в бриф проекта** — если проход не мог знать факта: объём, характер потока,
|
||||||
что здесь необратимо, какой шаг гейта красит безусловно. Самый частый адрес и
|
что здесь необратимо, какой шаг гейта красит безусловно. Самый частый адрес и
|
||||||
самый дешёвый.
|
самый дешёвый. Сюда же — **воспроизведённый случай** (раздел `## Прецеденты`:
|
||||||
|
класс, симптом, чем воспроизведён, чем закончилось) и **вопрос конкретному
|
||||||
|
проходу**, если промах лечится не фактом, а заданным вопросом (раздел
|
||||||
|
`## Вопросы к проходам`). Прежде чем править charter, проверь, не хватит ли
|
||||||
|
этих двух разделов: charter общий для всех проектов, бриф — про этот.
|
||||||
- **в конвенции или в правило линтера** — если свойство выражается
|
- **в конвенции или в правило линтера** — если свойство выражается
|
||||||
детерминированно (процедура — [promote.md](promote.md)).
|
детерминированно (процедура — [promote.md](promote.md)).
|
||||||
- **в charter прохода** — если сломан **метод**, а не знание. Правка charter'а
|
- **в charter прохода** — если сломан **метод**, а не знание. Правка charter'а
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ description: Проводит несколько задач разом — пл
|
|||||||
# Батч задач
|
# Батч задач
|
||||||
|
|
||||||
Оркестратор **набора** задач. Планирует порядок, раскидывает задачи по
|
Оркестратор **набора** задач. Планирует порядок, раскидывает задачи по
|
||||||
изолированным worktree, каждую проводит через полный цикл `task-pipeline`, затем
|
изолированным worktree, каждую проводит через полный цикл
|
||||||
сводит в основную ветку линейной историей и делает финальную сверку. Тонкая
|
`av-dev-pipeline:task-pipeline`, затем сводит в основную ветку линейной историей
|
||||||
обёртка над `task-pipeline` — не переизобретай её шаги, вызывай как есть.
|
и делает финальную сверку. Тонкая обёртка над пайплайном задачи — не
|
||||||
|
переизобретай её шаги, вызывай как есть.
|
||||||
|
|
||||||
Работай **максимально автономно**, по тому же принципу, что и одиночный пайплайн:
|
Работай **максимально автономно**, по тому же принципу, что и одиночный пайплайн:
|
||||||
вопрос, который решать не тебе, записывается и не останавливает поток; спрашиваем
|
вопрос, который решать не тебе, записывается и не останавливает поток; спрашиваем
|
||||||
@@ -16,8 +17,24 @@ description: Проводит несколько задач разом — пл
|
|||||||
рабочих данных). Механику — планирование, worktree, rebase, интеграцию, чистку —
|
рабочих данных). Механику — планирование, worktree, rebase, интеграцию, чистку —
|
||||||
делаем без спроса.
|
делаем без спроса.
|
||||||
|
|
||||||
|
## Предпосылки
|
||||||
|
|
||||||
|
- **OpenSpec и скиллы `opsx:*`** — на них стоит цикл внутри каждого сабагента и
|
||||||
|
проход `review-specs` финальной сверки. Проекта без OpenSpec это касается так
|
||||||
|
же, как одиночного пайплайна (см. его раздел «Предпосылки»).
|
||||||
|
- **Скиллы зовутся с пространством имён**: `av-dev-pipeline:task-pipeline`,
|
||||||
|
`av-dev-pipeline:review-pipeline`, `av-dev-pipeline:project-brief`. Короткое имя
|
||||||
|
может разрешиться в устаревшую проектную копию, и это произойдёт молча — в
|
||||||
|
charter'е сабагента пиши полное имя, он твоего контекста не видит.
|
||||||
|
- **Проектные копии этих скиллов и агентов при установке плагина удаляются.**
|
||||||
|
|
||||||
Перед стартом прочитай `CLAUDE.md` проекта и бриф ревью (`docs/review-brief.md`):
|
Перед стартом прочитай `CLAUDE.md` проекта и бриф ревью (`docs/review-brief.md`):
|
||||||
из него берутся команда гейта, инварианты и раскладка нумерованных артефактов.
|
из него берутся **основная ветка** (раздел `## Карта` — она подставляется в
|
||||||
|
каждую команду git ниже), команда гейта, инварианты и раскладка нумерованных
|
||||||
|
артефактов. Брифа нет — заведи его Skill'ом
|
||||||
|
**`av-dev-pipeline:project-brief`** один раз, до первой волны: иначе каждая
|
||||||
|
задача батча заплатит деградированным ревью, а имя основной ветки придётся
|
||||||
|
угадывать.
|
||||||
|
|
||||||
## Границы
|
## Границы
|
||||||
|
|
||||||
@@ -27,6 +44,9 @@ description: Проводит несколько задач разом — пл
|
|||||||
- **Батч не владеет спринтом и целями.** Он сообщает исход по каждой задаче в тех
|
- **Батч не владеет спринтом и целями.** Он сообщает исход по каждой задаче в тех
|
||||||
же трёх словах, что и `task-pipeline`: сделана / не доведена / оказалась крупнее
|
же трёх словах, что и `task-pipeline`: сделана / не доведена / оказалась крупнее
|
||||||
задачи.
|
задачи.
|
||||||
|
- **Записей учёта батч не трогает и задач не закрывает** — как и одиночный
|
||||||
|
пайплайн. Закрытие — акт владельца спринта после приёмки. Урожай ревью батч
|
||||||
|
отдаёт списком, а задачи из него заводит тот, кто ведёт задачи проекта.
|
||||||
|
|
||||||
## Ключевое отличие от одиночного пайплайна
|
## Ключевое отличие от одиночного пайплайна
|
||||||
|
|
||||||
@@ -62,25 +82,53 @@ fast-forward. Ветки после вливания удаляются.
|
|||||||
- **затронутые capability** — по её описанию и по каталогу актуальных спек
|
- **затронутые capability** — по её описанию и по каталогу актуальных спек
|
||||||
(`openspec/specs/`);
|
(`openspec/specs/`);
|
||||||
- **жёсткие зависимости**: задача B строится на результате A → A строго раньше B;
|
- **жёсткие зависимости**: задача B строится на результате A → A строго раньше B;
|
||||||
|
- **замеряющая задача** — та, чьё ревью будет доказывать находки **числами**, и
|
||||||
|
потому она гонится в волне **одна** (обоснование — ниже, в шаге 4). Решается
|
||||||
|
здесь, на планировании, а не во время прогона: состав волны определяется
|
||||||
|
сейчас, а профиль ревью сабагент выберет только внутри задачи, и ключевать
|
||||||
|
волну на ещё не сделанный выбор нельзя. Триггеры — по фактам о задаче, каждый
|
||||||
|
сам по себе достаточен:
|
||||||
|
- трогает схему хранилища, миграцию, формат на диске или объём хранимого;
|
||||||
|
- трогает конкурентность: транзакции, блокировки, фоновые циклы, общее
|
||||||
|
состояние;
|
||||||
|
- трогает размер тела, буфер, память, сжатие, ретеншен, темп потока;
|
||||||
|
- её тема названа в разделах `## Прод и поток` или `## Прецеденты` брифа как
|
||||||
|
место, где уже мерили или уже ломалось.
|
||||||
|
|
||||||
|
Ни один триггер не сработал — задача не замеряющая, даже если её ревью
|
||||||
|
окажется `deep`. `deep` про глубину проверки, замеряющая — про соревнование за
|
||||||
|
железо; это разные вопросы, и совпадают они не всегда;
|
||||||
- **нумерованные артефакты — номера раздаёт оркестратор заранее.** Если проект
|
- **нумерованные артефакты — номера раздаёт оркестратор заранее.** Если проект
|
||||||
нумерует миграции или подобные файлы (путь — из брифа), посмотри последний
|
нумерует миграции или подобные файлы (путь — из брифа), посмотри последний
|
||||||
номер и **раздай номера тем задачам, которые, вероятно, их добавят**, до
|
номер и **раздай номера всем задачам, которые, вероятно, их добавят**, до
|
||||||
запуска. Номер уходит в charter сабагента, и он берёт назначенный, а не
|
запуска. Номер уходит в charter сабагента, и он берёт назначенный, а не
|
||||||
«следующий свободный». Так такие задачи можно гнать одновременно: файлы не
|
«следующий свободный».
|
||||||
столкнутся, а описание схемы правят разные строки — конфликт мелкий и решается
|
|
||||||
на интеграции;
|
**Это отдельная механика от правила волны, и она ему не служит** — их раньше
|
||||||
|
путали, и они тянули в разные стороны. Правило волны отвечает на вопрос «кто с
|
||||||
|
кем гонится одновременно», предраздача — на вопрос «какой номер берёт задача».
|
||||||
|
Раздача нужна там, где **две задачи одной под-пачки** добавляют нумерованный
|
||||||
|
артефакт: каждая считает «следующий свободный» по основной ветке, которая ещё
|
||||||
|
не видела соседку, и обе берут один номер. Миграции под это почти не попадают —
|
||||||
|
миграция и так триггер замеряющей задачи, а замеряющая идёт одна; но
|
||||||
|
нумерованные артефакты бывают не только миграциями. Поэтому номера раздаются
|
||||||
|
**всем** задачам с таким артефактом, независимо от того, в какой волне они
|
||||||
|
окажутся: раздача ничего не стоит, а её отсутствие ловится только конфликтом на
|
||||||
|
интеграции. Между волнами проблемы нет — ветка следующей волны берётся от
|
||||||
|
вершины, уже включающей предыдущие;
|
||||||
- **жёстко сериализуем** (не гоняем одновременно) настоящие пересечения:
|
- **жёстко сериализуем** (не гоняем одновременно) настоящие пересечения:
|
||||||
- **одна capability на несколько задач** — две задачи, правящие одну спеку (тем
|
- **одна capability на несколько задач** — две задачи, правящие одну спеку (тем
|
||||||
более одно и то же `### Requirement`), дают не текстовый, а **семантический**
|
более одно и то же `### Requirement`), дают не текстовый, а **семантический**
|
||||||
конфликт при архивации; сериализуем по смыслу, а не только по файлам;
|
конфликт при архивации; сериализуем по смыслу, а не только по файлам;
|
||||||
- пересечение по одним и тем же исходникам;
|
- пересечение по одним и тем же исходникам;
|
||||||
- **мягкие конфликты** сериализовать не надо: индекс беклога (каждая задача
|
- **мягкие конфликты** сериализовать не надо: файлы-перечни, где каждая задача
|
||||||
убирает свою строку) и спеки разных capability — разные строки и файлы,
|
правит **свою** строку (индексы, оглавления, списки записей), и спеки разных
|
||||||
сливаются сами.
|
capability — разные строки и файлы, сливаются сами.
|
||||||
|
|
||||||
Собери план: **волны** параллельно-безопасных задач плюс сериализованный хвост
|
Собери план: **волны** параллельно-безопасных задач плюс сериализованный хвост
|
||||||
конфликтоопасных, с учётом зависимостей. Покажи план короткой репликой и иди
|
конфликтоопасных, с учётом зависимостей; замеряющие задачи стоят в плане
|
||||||
дальше.
|
отдельными волнами по одной. Покажи план короткой репликой — назвав, какие
|
||||||
|
задачи признаны замеряющими и по какому триггеру, — и иди дальше.
|
||||||
|
|
||||||
### 3. Свежая база
|
### 3. Свежая база
|
||||||
|
|
||||||
@@ -91,16 +139,17 @@ fast-forward. Ветки после вливания удаляются.
|
|||||||
### 4. Прогнать волны
|
### 4. Прогнать волны
|
||||||
|
|
||||||
**Потолок параллелизма — 2–3 задачи одновременно.** Каждая задача тянет полный
|
**Потолок параллелизма — 2–3 задачи одновременно.** Каждая задача тянет полный
|
||||||
`task-pipeline` с вложенным ревью и гейтом, поэтому больше трёх разом душат
|
цикл пайплайна с вложенным ревью и гейтом, поэтому больше трёх разом душат
|
||||||
машину и провоцируют гонки. Волну шире трёх бей на под-пачки по ≤3.
|
машину и провоцируют гонки. Волну шире трёх бей на под-пачки по ≤3 и **гони
|
||||||
|
под-пачки последовательно**: следующая стартует, когда предыдущая вернула отчёты.
|
||||||
|
Иначе потолок обходится тривиально — шесть задач, запущенных «двумя под-пачками»
|
||||||
|
в одном сообщении, это шесть задач разом.
|
||||||
|
|
||||||
**Волна из одной задачи — не вырожденный случай, а обязательный.** Задача,
|
**Волна из одной задачи — не вырожденный случай, а обязательный.** Задача,
|
||||||
ревью которой будет доказывать находки **числами** (профиль `deep`, где работают
|
признанная на шаге 2 **замеряющей**, гонится в волне одна: соседний прогон на той
|
||||||
`adversary` и `ops`: удержание блокировки, пик памяти, рост файлов, длительность
|
же машине портит числа, а находка с испорченным оракулом хуже отсутствующей — она
|
||||||
операции), гонится в волне одна. Соседний прогон на той же машине портит эти
|
выглядит доказанной. Если замеряющая задача всё же пошла в общей волне, её отчёт
|
||||||
числа, а находка с испорченным оракулом хуже отсутствующей — она выглядит
|
обязан нести строку в границах покрытия: замеры сняты под соседней нагрузкой.
|
||||||
доказанной. Если задача всё же пошла в общей волне, её отчёт обязан нести строку
|
|
||||||
в границах покрытия: замеры сняты под соседней нагрузкой.
|
|
||||||
|
|
||||||
Для каждой задачи в под-пачке:
|
Для каждой задачи в под-пачке:
|
||||||
|
|
||||||
@@ -111,19 +160,28 @@ fast-forward. Ветки после вливания удаляются.
|
|||||||
`subagent_type: general-purpose`. Charter сабагента:
|
`subagent_type: general-purpose`. Charter сабагента:
|
||||||
- работай **строго в своём worktree** `<path>`; в другие каталоги и в основную
|
- работай **строго в своём worktree** `<path>`; в другие каталоги и в основную
|
||||||
ветку не лезь;
|
ветку не лезь;
|
||||||
- прогони Skill **`task-pipeline`** ровно на этой задаче, полный цикл SDD с
|
- прогони Skill **`av-dev-pipeline:task-pipeline`** ровно на этой задаче,
|
||||||
обоими чекпоинтами ревью;
|
полный цикл SDD с обоими чекпоинтами ревью;
|
||||||
- если задаче назначен **номер артефакта** — используй строго его;
|
- если задаче назначен **номер артефакта** — используй строго его;
|
||||||
- **профиль ревью выбирается по факту изменения.** Батч не повод понижать
|
- **профиль ревью выбирается по факту изменения.** Батч не повод понижать
|
||||||
профиль: «нас много и мы спешим» — это ровно тот стимул, из-за которого
|
профиль: «нас много и мы спешим» — это ровно тот стимул, из-за которого
|
||||||
проходы пропускают;
|
проходы пропускают;
|
||||||
- **режим прогона проходов — последовательный.** Твой worktree не один на
|
- **режим прогона проходов — последовательный.** Твой worktree не один на
|
||||||
машине;
|
машине;
|
||||||
|
- **если вложенные сабагенты недоступны** (движок не даёт запускать агентов из
|
||||||
|
агента) — не пропускай ревью и не понижай профиль: проведи его **инлайн** по
|
||||||
|
тем же charter'ам `av-dev-pipeline`, сохранив обязательное — гейт до
|
||||||
|
опиниативных проходов, состав по профилю, триаж последним. И **скажи в
|
||||||
|
отчёте прямым текстом, что ревью шло инлайн**: инлайновый проход видит
|
||||||
|
контекст автора и потому декоррелирован слабее — это меняет доверие к
|
||||||
|
результату, а не только способ запуска;
|
||||||
- **вернуть отчёт**, в котором обязательно: исход задачи одним из трёх слов;
|
- **вернуть отчёт**, в котором обязательно: исход задачи одним из трёх слов;
|
||||||
что сделано; какие вопросы записаны и куда; изменённые файлы; добавлялся ли
|
**объявленный профиль ревью и режим прогона**; что сделано; какие вопросы
|
||||||
нумерованный артефакт и с каким номером; затронутые capability; состояние
|
записаны и куда; изменённые файлы; добавлялся ли нумерованный артефакт и с
|
||||||
гейта; **перечень запущенных проходов ревью поимённо с исходом каждого** и
|
каким номером; затронутые capability; состояние гейта; **перечень
|
||||||
границы покрытия.
|
запущенных проходов ревью поимённо с исходом каждого**; **путь к
|
||||||
|
сохранённому отчёту триажа** (`openspec/changes/<id>/review/`); шло ли ревью
|
||||||
|
инлайн; границы покрытия.
|
||||||
|
|
||||||
Сабагент, упершийся в вопрос, **не останавливает батч**: он записывает вопрос,
|
Сабагент, упершийся в вопрос, **не останавливает батч**: он записывает вопрос,
|
||||||
режет задачу до остатка и доводит остаток — либо, если остатка нет, возвращает
|
режет задачу до остатка и доводит остаток — либо, если остатка нет, возвращает
|
||||||
@@ -132,40 +190,86 @@ fast-forward. Ветки после вливания удаляются.
|
|||||||
|
|
||||||
### 5. Проверить полноту ревью — до интеграции
|
### 5. Проверить полноту ревью — до интеграции
|
||||||
|
|
||||||
**Ветка, чей отчёт не называет проходы поимённо, не вливается.** Пропуск прохода
|
**Ветка, чей отчёт не называет профиль и проходы поимённо, не вливается.**
|
||||||
не отличим от прохода без находок, и на уровне батча это ещё опаснее: отчётов
|
Пропуск прохода не отличим от прохода без находок, и на уровне батча это ещё
|
||||||
много, каждый выглядит полным, а сверять их некому, кроме тебя.
|
опаснее: отчётов много, каждый выглядит полным, а сверять их некому, кроме тебя.
|
||||||
|
|
||||||
По каждой готовой ветке сверь перечень проходов с таблицей профилей скилла
|
Сверка идёт в три шага, и порядок важен:
|
||||||
`review-pipeline` для объявленного профиля. Расхождение — не повод отменять
|
|
||||||
задачу: дозапусти недостающие проходы **на ветке**, в её worktree, через
|
1. **Возьми объявленный профиль** из отчёта задачи — он затем и заказан в
|
||||||
`review-pipeline`, и только потом интегрируй. Отчёт дозапуска приложи к отчёту
|
обязательных полях шага 4. Профиля в отчёте нет — перечень проходов сверять
|
||||||
задачи.
|
не с чем; это само по себе основание не вливать, пока сабагент не назовёт
|
||||||
|
профиль и не обоснует его по факту изменения.
|
||||||
|
2. **Сверяй с независимым артефактом, а не с прозой отчёта.** Перечень проходов
|
||||||
|
бери из **сохранённого отчёта триажа** (`openspec/changes/<id>/review/`) —
|
||||||
|
пайплайн обязан его туда положить. Проза сабагента написана тем же, кто мог
|
||||||
|
проход и пропустить: она подтверждает сама себя. Отчёта триажа на месте нет —
|
||||||
|
считай, что состав неизвестен, и дозапускай ревью целиком.
|
||||||
|
3. **Сверь состав** с таблицей профилей скилла
|
||||||
|
`av-dev-pipeline:review-pipeline` для объявленного профиля.
|
||||||
|
|
||||||
|
Расхождение — не повод отменять задачу: дозапусти недостающие проходы **на
|
||||||
|
ветке**, в её worktree, через `av-dev-pipeline:review-pipeline`, и только потом
|
||||||
|
интегрируй.
|
||||||
|
|
||||||
|
**Находки дозапуска — такие же находки, и зелёный гейт их не отменяет.** Правило
|
||||||
|
интеграции «вливаем только зелёные» смотрит на гейт, а дозапущенный `critical`
|
||||||
|
гейт не красит: он был бы пропущен молча, если это не сказать прямо. Поэтому:
|
||||||
|
|
||||||
|
- `critical` или `major` из дозапуска — **вливание этой ветки останавливается**.
|
||||||
|
Помеченное `инлайн` чинится в её worktree, после починки — гейт, затем
|
||||||
|
интеграция. Помеченное `развилка` — вопрос в запись, задача режется до остатка
|
||||||
|
ровно так же, как это сделал бы пайплайн внутри;
|
||||||
|
- остатка нет — ветка не вливается и уходит в доклад как провалившаяся, со своим
|
||||||
|
worktree;
|
||||||
|
- `minor` и `nit` из дозапуска — в урожай доклада, вливанию не мешают.
|
||||||
|
|
||||||
|
Отчёт дозапуска приложи к отчёту задачи и назови в докладе (шаг 9), почему он
|
||||||
|
понадобился: систематический пропуск одного и того же прохода — находка о самом
|
||||||
|
конвейере, а не о задаче.
|
||||||
|
|
||||||
### 6. Интегрировать — rebase + fast-forward, по одной ветке
|
### 6. Интегрировать — rebase + fast-forward, по одной ветке
|
||||||
|
|
||||||
Сводим ветки **строго последовательно** (линейная история), в порядке
|
Сводим ветки **строго последовательно** (линейная история), в порядке
|
||||||
зависимостей. Вливаем **только зелёные**.
|
зависимостей. Вливаем **только зелёные**.
|
||||||
|
|
||||||
|
**Ветка задачи занята её worktree, и это определяет форму команд.** Пока worktree
|
||||||
|
жив (а удаляется он последним, после зелёного гейта), ветка `task/<slug>`
|
||||||
|
checkout'нута в нём, и `git rebase <основная> task/<slug>` из главного worktree
|
||||||
|
**падает**: `fatal: 'task/<slug>' is already used by worktree at …`. Поэтому
|
||||||
|
rebase делается **внутри worktree задачи**, а ff-слияние — из главного.
|
||||||
|
|
||||||
Для каждой готовой ветки `task/<slug>`:
|
Для каждой готовой ветки `task/<slug>`:
|
||||||
|
|
||||||
- `git rebase <основная> task/<slug>` — перенос на текущую вершину;
|
- `git -C <path> rebase <основная>` — перенос ветки задачи на текущую вершину,
|
||||||
|
выполняется в её собственном worktree;
|
||||||
- резолв конфликтов (их почти нет — конфликтоопасное сериализовано, номера
|
- резолв конфликтов (их почти нет — конфликтоопасное сериализовано, номера
|
||||||
розданы заранее). Неавтоматический конфликт — **не форсируй**: прерви
|
розданы заранее). Неавтоматический конфликт — **не форсируй**: прерви
|
||||||
(`git rebase --abort`), оставь ветку и worktree как есть, вынеси это в доклад
|
(`git -C <path> rebase --abort`), оставь ветку и worktree как есть, вынеси это
|
||||||
как нераспознанное пересечение;
|
в доклад как нераспознанное пересечение;
|
||||||
- `git checkout <основная> && git merge --ff-only task/<slug>`;
|
- **ненулевой код `rebase` относится к этой ветке и только к ней.** Прерванный
|
||||||
|
rebase в чужом worktree не трогает ни главное дерево, ни остальные ветки:
|
||||||
|
проверь `git -C <path> status` и `git status` — обе чистые. Уводить весь батч
|
||||||
|
в провалившиеся из-за одного ненулевого кода запрещено: это ложная причина,
|
||||||
|
из-за которой зелёные задачи не доедут до основной ветки. Провалилась одна —
|
||||||
|
провалилась одна;
|
||||||
|
- из главного worktree (он стоит на основной ветке — проверь
|
||||||
|
`git rev-parse --abbrev-ref HEAD`): `git merge --ff-only task/<slug>`. Ветку в
|
||||||
|
главном дереве **не переключай** — `git checkout task/<slug>` тоже упрётся в
|
||||||
|
занятость;
|
||||||
- после каждой интеграции — **гейт на основной ветке**. Красное — **откати эту
|
- после каждой интеграции — **гейт на основной ветке**. Красное — **откати эту
|
||||||
интеграцию** (`git reset --hard` на прошлую вершину), ветку с worktree сохрани,
|
интеграцию** (`git reset --hard` на прошлую вершину), ветку с worktree сохрани,
|
||||||
задачу перечисли в докладе. Основная ветка **никогда** не остаётся
|
задачу перечисли в докладе. Основная ветка **никогда** не остаётся
|
||||||
полузелёной;
|
полузелёной;
|
||||||
- только после зелёного: `git worktree remove <path>` и
|
- только после зелёного: `git worktree remove <path>` и
|
||||||
`git branch -d task/<slug>`.
|
`git branch -d task/<slug>` — в этом порядке, иначе ветка снова занята.
|
||||||
|
|
||||||
**Политика частичного провала.** Упавшая задача (исход «не доведена», красные
|
**Политика частичного провала.** Упавшая задача (исход «не доведена», красные
|
||||||
тесты в её worktree, конфликт при rebase) **не блокирует остальные**: интегрируем
|
тесты в её worktree, конфликт при rebase, невлитая из-за находок дозапуска)
|
||||||
все зелёные, упавшую оставляем в её worktree и ветке нетронутой — ничего не
|
**не блокирует остальные**: интегрируем все зелёные, упавшую оставляем в её
|
||||||
удаляем, — и перечисляем в докладе с причиной, отчётом и путём к worktree.
|
worktree и ветке нетронутой — ничего не удаляем, — и перечисляем в докладе с
|
||||||
|
причиной, отчётом и путём к worktree. Причина называется **настоящая**: «конфликт
|
||||||
|
rebase в файле X», а не «нераспознанное пересечение» на всякий случай.
|
||||||
|
|
||||||
### 7. Финальный гейт
|
### 7. Финальный гейт
|
||||||
|
|
||||||
@@ -191,7 +295,7 @@ fast-forward. Ветки после вливания удаляются.
|
|||||||
уже делается» — именно он возникает, когда две задачи независимо решали
|
уже делается» — именно он возникает, когда две задачи независимо решали
|
||||||
похожее.
|
похожее.
|
||||||
|
|
||||||
Замечания отрабатывай как в `task-pipeline`: `инлайн` чини сам, `развилка` —
|
Замечания отрабатывай как одиночный пайплайн: `инлайн` чини сам, `развилка` —
|
||||||
вопросом в запись; после правок — снова гейт.
|
вопросом в запись; после правок — снова гейт.
|
||||||
|
|
||||||
### 9. Прибраться и доложить
|
### 9. Прибраться и доложить
|
||||||
@@ -199,16 +303,22 @@ fast-forward. Ветки после вливания удаляются.
|
|||||||
- Убери worktree и ветки **только успешно влитых** задач, в конце
|
- Убери worktree и ветки **только успешно влитых** задач, в конце
|
||||||
`git worktree prune`. Worktree и ветки **провалившихся** не трогай — они нужны
|
`git worktree prune`. Worktree и ветки **провалившихся** не трогай — они нужны
|
||||||
для ручного дожатия.
|
для ручного дожатия.
|
||||||
|
- **Задачи батч не закрывает** — ни одну, ни свои, ни чужие записи учёта не
|
||||||
|
трогает. Он сообщает исход по каждой; закрытие происходит после приёмки и
|
||||||
|
делается владельцем спринта.
|
||||||
- Доложи кратко:
|
- Доложи кратко:
|
||||||
- **исход по каждой задаче** одним из трёх слов, с хешем коммита;
|
- **исход по каждой задаче** одним из трёх слов, с хешем коммита;
|
||||||
- план волн и порядок интеграции;
|
- план волн и порядок интеграции, с пометкой, какие задачи шли по одной как
|
||||||
|
замеряющие;
|
||||||
- вопросы, записанные сабагентами, пачкой;
|
- вопросы, записанные сабагентами, пачкой;
|
||||||
- что дозапускалось на шаге 5 и почему;
|
- что дозапускалось на шаге 5 и почему; шло ли где-то ревью инлайн;
|
||||||
- итог финальной сверки и ссылки на архивные change;
|
- итог финальной сверки и ссылки на архивные change;
|
||||||
- **отдельно — провалившиеся** задачи с причиной и путём к оставленному
|
- **`Урожай`** — отложенные находки всех задач одним списком, с провенансом.
|
||||||
worktree;
|
Задачи из него заводит тот, кто ведёт задачи проекта, а не батч;
|
||||||
|
- **отдельно — провалившиеся** задачи с настоящей причиной и путём к
|
||||||
|
оставленному worktree;
|
||||||
- **границы покрытия сводной строкой**, включая задачи, чьи замеры снимались в
|
- **границы покрытия сводной строкой**, включая задачи, чьи замеры снимались в
|
||||||
общей волне.
|
общей волне, и ветки, где ревью шло инлайн.
|
||||||
|
|
||||||
## Тонкости
|
## Тонкости
|
||||||
|
|
||||||
@@ -219,9 +329,9 @@ fast-forward. Ветки после вливания удаляются.
|
|||||||
- Поведенческая верификация внутри сабагента поднимает изменение вживую: следи,
|
- Поведенческая верификация внутри сабагента поднимает изменение вживую: следи,
|
||||||
чтобы соседние worktree не дрались за порты и рабочие каталоги. Если проект
|
чтобы соседние worktree не дрались за порты и рабочие каталоги. Если проект
|
||||||
умеет поднимать только один экземпляр — такие задачи в одну волну не ставь.
|
умеет поднимать только один экземпляр — такие задачи в одну волну не ставь.
|
||||||
- Ревью выполненного — **до** закрытия задачи; это забота `task-pipeline` внутри
|
- Ревью выполненного — **до** интеграции; это забота
|
||||||
каждого сабагента, дублировать не надо.
|
`av-dev-pipeline:task-pipeline` внутри каждого сабагента, дублировать не надо.
|
||||||
- `openspec validate --strict` тоже внутри `task-pipeline` — не пропускай его
|
- `openspec validate --strict` тоже внутри пайплайна задачи — не пропускай его
|
||||||
своими правками на интеграции.
|
своими правками на интеграции.
|
||||||
- Крупная переработка, предложенная ревью внутри задачи, — развилка: не вливай
|
- Крупная переработка, предложенная ревью внутри задачи, — развилка: не вливай
|
||||||
молча, вынеси в доклад.
|
молча, вынеси в доклад.
|
||||||
|
|||||||
@@ -10,18 +10,48 @@ description: Автономно проводит одну задачу чере
|
|||||||
|
|
||||||
Это тонкая обёртка над каноническими скиллами `opsx:explore` / `opsx:propose` /
|
Это тонкая обёртка над каноническими скиллами `opsx:explore` / `opsx:propose` /
|
||||||
`opsx:apply` / `opsx:archive` — вызывай их через Skill, не переизобретай их шаги.
|
`opsx:apply` / `opsx:archive` — вызывай их через Skill, не переизобретай их шаги.
|
||||||
Ревью — скилл `review-pipeline`, он же держит правило выбора профиля.
|
Ревью — скилл `av-dev-pipeline:review-pipeline`, он же держит правило выбора
|
||||||
|
профиля.
|
||||||
|
|
||||||
|
## Предпосылки
|
||||||
|
|
||||||
|
- **OpenSpec и скиллы `opsx:*`** — внешняя обвязка, на которой стоят шаги 2, 3, 6
|
||||||
|
и 8, а также проход `review-specs` и профиль `design` (они завязаны на
|
||||||
|
`openspec/changes/<id>/specs/*/spec.md` и на `openspec validate --strict`). В
|
||||||
|
проекте без OpenSpec эти шаги упадут на «нет такого скилла»: либо подключаем
|
||||||
|
OpenSpec, либо цикл вырождается в «прочитать задачу → код → ревью кода →
|
||||||
|
коммит», и об отсутствии спекового контура говорится в докладе.
|
||||||
|
- **Скиллы зовутся с пространством имён** — `av-dev-pipeline:review-pipeline`,
|
||||||
|
`av-dev-pipeline:project-brief`. Короткое имя может разрешиться в устаревшую
|
||||||
|
проектную копию, и это произойдёт молча.
|
||||||
|
- **Проектные копии этих скиллов и агентов удаляются при установке плагина**
|
||||||
|
(`.claude/skills/{task-pipeline,review-pipeline,task-batch}`,
|
||||||
|
`.claude/agents/<проект>-review-*.md`). Две копии одного скилла расходятся, и
|
||||||
|
побеждает та, что короче названа.
|
||||||
|
|
||||||
Перед стартом прочитай `CLAUDE.md` проекта и то, на что он ссылается
|
Перед стартом прочитай `CLAUDE.md` проекта и то, на что он ссылается
|
||||||
(архитектура, конвенции), если ещё не в контексте. Проектные факты, нужные ревью
|
(архитектура, конвенции), если ещё не в контексте. Проектные факты, нужные ревью
|
||||||
— инварианты, команда гейта, объёмы, модель угроз, — живут в брифе
|
— инварианты, команда гейта, объёмы, модель угроз, прецеденты, — живут в брифе
|
||||||
(`docs/review-brief.md`, контракт — в references конвейера ревью).
|
(`docs/review-brief.md`, контракт — в references конвейера ревью).
|
||||||
|
|
||||||
|
**Брифа нет ни по одному пути — заведи его, а не работай в деградированном
|
||||||
|
режиме.** Вызови Skill **`av-dev-pipeline:project-brief`**: он соберёт бриф из
|
||||||
|
`CLAUDE.md`, архитектуры, файла задач и конвенций, покажет человеку и вернёт
|
||||||
|
путь, который дальше передаётся ревью. Это механика: спрашивать разрешения не
|
||||||
|
нужно. Один шаг один раз на проект — против деградации на каждой задаче.
|
||||||
|
|
||||||
## Границы: чем пайплайн не владеет
|
## Границы: чем пайплайн не владеет
|
||||||
|
|
||||||
- **Беклогом, спринтом, целями и приоритетами.** Задача приходит извне. Пайплайн
|
- **Беклогом, спринтом, целями и приоритетами.** Задача приходит извне. Пайплайн
|
||||||
её не выбирает, не приоритизирует, не заводит и не переоценивает; если в
|
её не выбирает, не приоритизирует, не заводит и не переоценивает; если в
|
||||||
проекте есть свой процесс управления задачами — он и решает, что брать.
|
проекте есть свой процесс управления задачами — он и решает, что брать.
|
||||||
|
- **Записями учёта.** Пайплайн **не закрывает задачу**, не двигает её по
|
||||||
|
статусам, не правит индекс и не зовёт скриптов учёта. Он сообщает исход;
|
||||||
|
закрытие — акт владельца спринта **после приёмки**, и оно происходит снаружи.
|
||||||
|
Закрыть задачу самому — значит закрыть её до коммита и до всякой приёмки, то
|
||||||
|
есть заверить собственную работу.
|
||||||
|
- **Заведением задач из урожая ревью.** Отложенные находки отдаются **списком**
|
||||||
|
(см. шаг 7); превращать их в задачи — работа того, кто ведёт задачи проекта.
|
||||||
- **Определением ценности.** «Нужна ли эта функциональность» — не вопрос
|
- **Определением ценности.** «Нужна ли эта функциональность» — не вопрос
|
||||||
пайплайна ни на одном шаге.
|
пайплайна ни на одном шаге.
|
||||||
|
|
||||||
@@ -47,12 +77,17 @@ description: Автономно проводит одну задачу чере
|
|||||||
поимённо, непущенные проходы названы в границах покрытия;
|
поимённо, непущенные проходы названы в границах покрытия;
|
||||||
3. change заархивирован, дельты влиты в актуальные спеки;
|
3. change заархивирован, дельты влиты в актуальные спеки;
|
||||||
4. коммит сделан в текущую ветку;
|
4. коммит сделан в текущую ветку;
|
||||||
5. **критерии приёмки, если проект их дал**, проверены поимённо, у каждого назван
|
5. **критерии приёмки, если проект их дал, выписаны поимённо, и по каждому назван
|
||||||
оракул и исход. Критерии приходят снаружи; пайплайн их не сочиняет и не
|
оракул и наблюдаемый исход** — «прогнал вот это, увидел вот то». Это **доклад,
|
||||||
занижает. Расхождение «критерии закрыты, а суть задачи не достигнута» — дефект
|
а не сертификация: приёмка — не работа пайплайна.** Исполнитель, ставящий себе
|
||||||
критериев, и о нём сообщается, а не молча дорабатывается.
|
галочку «принято», проверяет свою работу своим же взглядом — по границе это
|
||||||
|
может делать только декоррелированный приёмщик. Критерии приходят снаружи;
|
||||||
|
пайплайн их не сочиняет и не занижает. Расхождение «по каждому критерию исход
|
||||||
|
есть, а суть задачи не достигнута» — дефект критериев, и о нём сообщается, а
|
||||||
|
не молча дорабатывается.
|
||||||
|
|
||||||
Пункты 1–4 — своё. Пункт 5 — внешнее, и проверяется только если оно дано.
|
Пункты 1–4 — своё. Пункт 5 — внешнее: пайплайн доводит его до наблюдаемого
|
||||||
|
исхода и передаёт дальше.
|
||||||
|
|
||||||
## Принцип автономности
|
## Принцип автономности
|
||||||
|
|
||||||
@@ -73,13 +108,23 @@ description: Автономно проводит одну задачу чере
|
|||||||
3. Доведи остаток до конца и закоммить. Задача не «висит на вопросе», она сделана
|
3. Доведи остаток до конца и закоммить. Задача не «висит на вопросе», она сделана
|
||||||
в объявленных границах.
|
в объявленных границах.
|
||||||
|
|
||||||
**Что остатком не является** — две оговорки, без которых правило вредит:
|
**Что остатком не является — правило живёт не здесь.** Канонический текст с обеими
|
||||||
|
оговорками — в плагине `av-dev-tasks`, скилл `av-dev-tasks:session`, раздел
|
||||||
|
`## Вопрос, блокер, необратимое`, подраздел «Отличать вопрос от застревания».
|
||||||
|
Правило принадлежит управлению задачами, потому что решает **сделана задача или
|
||||||
|
вышла**, — это исход планирования, а не исполнения. **Ссылайся, не
|
||||||
|
пересказывай:** копия, заведённая здесь, уже однажды разошлась с оригиналом и
|
||||||
|
потеряла из перечня самое необратимое — запись **наружу**.
|
||||||
|
|
||||||
- **остаток, который записывает в хранилище или в журнал состояние, зависящее от
|
Коротко, чтобы знать, когда идти читать: остаток проверяется двумя порогами —
|
||||||
нерешённого, — не остаток.** Решение поднимается до начала записи. Иначе
|
**материализация нерешённого** (запись состояния, зависящего от неотвеченного
|
||||||
нерешённое материализуется в данные, а данные переживают решение;
|
вопроса) и **пол по пользе** (из остатка пропала польза, названная в постановке).
|
||||||
- **остаток, из которого пропала польза, названная в постановке, — не остаток.**
|
Оба порога — стоп: первый поднимает решение до начала записи, второй даёт исход
|
||||||
Это исход «не доведена», а не «сделана в границах».
|
«не доведена».
|
||||||
|
|
||||||
|
Плагин `av-dev-tasks` не подключён — правило не отменяется, а становится
|
||||||
|
осторожнее: прежде чем записать зависящее от нерешённого куда бы то ни было —
|
||||||
|
в хранилище, в журнал, в витрину или наружу, — спрашивай человека.
|
||||||
|
|
||||||
Нет полезного остатка — задача заканчивается исходом «не доведена», вопрос
|
Нет полезного остатка — задача заканчивается исходом «не доведена», вопрос
|
||||||
записан, ничего не коммитится наполовину.
|
записан, ничего не коммитится наполовину.
|
||||||
@@ -137,10 +182,10 @@ description: Автономно проводит одну задачу чере
|
|||||||
|
|
||||||
### 4. (Нетривиальная) Ревью предложения — профиль `design`, ДО кода
|
### 4. (Нетривиальная) Ревью предложения — профиль `design`, ДО кода
|
||||||
|
|
||||||
Первый чекпоинт. Вызови Skill **`review-pipeline`** с профилем `design`, ссылкой
|
Первый чекпоинт. Вызови Skill **`av-dev-pipeline:review-pipeline`** с профилем
|
||||||
на change `<id>` и путём к брифу. Он запустит `review-specs` (режим «дизайн ДО
|
`design`, ссылкой на change `<id>` и путём к брифу. Он запустит `review-specs`
|
||||||
кода»), `review-rubric` (фаза 1: приёмочные критерии для задуманного узла) и
|
(режим «дизайн ДО кода»), `review-rubric` (фаза 1: приёмочные критерии для
|
||||||
`review-architecture` по предложению.
|
задуманного узла) и `review-architecture` по предложению.
|
||||||
|
|
||||||
Смысл профиля: архитектурная находка на готовом коде стоит переписывания и
|
Смысл профиля: архитектурная находка на готовом коде стоит переписывания и
|
||||||
потому игнорируется — та же находка здесь стоит абзаца обсуждения. Рубрику из
|
потому игнорируется — та же находка здесь стоит абзаца обсуждения. Рубрику из
|
||||||
@@ -170,17 +215,16 @@ description: Автономно проводит одну задачу чере
|
|||||||
**Сервис не оставляем лежать.** Если запуск упал — почини или откати до конца
|
**Сервис не оставляем лежать.** Если запуск упал — почини или откати до конца
|
||||||
шага.
|
шага.
|
||||||
|
|
||||||
### 7. Ревью кода — Skill `review-pipeline`
|
### 7. Ревью кода — Skill `av-dev-pipeline:review-pipeline`
|
||||||
|
|
||||||
Второй чекпоинт. Вызови Skill **`review-pipeline`**, дав ссылку на change `<id>`,
|
Второй чекпоинт. Вызови Skill **`av-dev-pipeline:review-pipeline`**, дав ссылку
|
||||||
базу диффа, путь к брифу, профиль **и режим запуска**. Профиль выбирается по
|
на change `<id>`, базу диффа, путь к брифу, профиль **и режим запуска**.
|
||||||
факту изменения, а не по ощущению важности; общее правило — в скилле, проектные
|
|
||||||
триггеры — в брифе:
|
|
||||||
|
|
||||||
- миграция схемы, новый пакет, публичный контракт, правило идентичности или
|
**Правило выбора профиля живёт в скилле конвейера** (раздел «Профили»), проектные
|
||||||
слияния данных → `deep`;
|
триггеры — в разделе `## Триггеры` брифа. Здесь оно не пересказывается: три
|
||||||
- иначе меняется поведение, видимое снаружи → `standard`;
|
копии одного правила расходятся, и работать будет та, которую прочитали
|
||||||
- иначе (багфикс, локальная правка, доки) → `quick`.
|
последней. Помни ровно одно — **профиль выбирается по факту изменения, а не по
|
||||||
|
ощущению важности**, и посмотри таблицу перед вызовом.
|
||||||
|
|
||||||
**Режим по умолчанию последовательный, и обосновывать его не надо.** Параллельно
|
**Режим по умолчанию последовательный, и обосновывать его не надо.** Параллельно
|
||||||
гоняем только тогда, когда об этом попросили явно **и назвали набор** — какие
|
гоняем только тогда, когда об этом попросили явно **и назвали набор** — какие
|
||||||
@@ -195,41 +239,54 @@ description: Автономно проводит одну задачу чере
|
|||||||
покрытия.
|
покрытия.
|
||||||
|
|
||||||
**Сверь состав прогона с таблицей профилей в скилле, прежде чем коммитить.**
|
**Сверь состав прогона с таблицей профилей в скилле, прежде чем коммитить.**
|
||||||
Пропуск прохода не отличим от прохода без находок: гейт зелёный, спеки сошлись,
|
Отчёт обязан называть запущенные проходы **поимённо и с исходом**; непущенный
|
||||||
отчёт выглядит полным. Единственный, кто мог бы заметить пропуск, — триаж, а он
|
идёт строкой «не запускался» в границы покрытия. Реестр короткий (4–8 проходов) —
|
||||||
заполняется тем, что ему подали. Отчёт обязан называть запущенные проходы
|
сверка стоит одного взгляда. Почему это правило существует, объясняет раздел
|
||||||
**поимённо и с исходом**; непущенный идёт строкой «не запускался» в границы
|
«Профили» скилла конвейера; здесь — само требование.
|
||||||
покрытия. Реестр короткий (4–8 проходов) — сверка стоит одного взгляда, а
|
|
||||||
молчащий пропуск уже стоил семи находок и отдельной задачи на их дозакрытие.
|
|
||||||
|
|
||||||
Отработай так же, как шаг 5: помеченное `инлайн` чини сам и не логируй,
|
Отработай так же, как шаг 5: помеченное `инлайн` чини сам и не логируй,
|
||||||
`развилка` — вопросом в запись (он уже сформулирован триажем, его остаётся
|
`развилка` — вопросом в запись (он уже сформулирован триажем, его остаётся
|
||||||
перенести). После правок — снова гейт.
|
перенести). После правок — снова гейт.
|
||||||
|
|
||||||
|
**Урожай — списком, не задачами.** Отложенные находки (реальный `major` не для
|
||||||
|
этого мерджа, развилка, решённая «потом», пачка `nit`) собери в секцию доклада
|
||||||
|
`Урожай`: формулировка, оракул, провенанс. Задачи из него **заводит не пайплайн**
|
||||||
|
— у того, кто ведёт задачи проекта, своя нарезка, свой формат и свои правила
|
||||||
|
дублей. Твоя обязанность — не потерять и передать.
|
||||||
|
|
||||||
**Границы покрытия из отчёта не выбрасывай** — они уезжают в финальный доклад
|
**Границы покрытия из отчёта не выбрасывай** — они уезжают в финальный доклад
|
||||||
сжатой строкой. Отчёт, из которого исчезло «что проверить было невозможно»,
|
сжатой строкой. Отчёт, из которого исчезло «что проверить было невозможно»,
|
||||||
превращается в ложное ощущение проверенности.
|
превращается в ложное ощущение проверенности.
|
||||||
|
|
||||||
Отчёт триажа сохрани вместе с change (`openspec/changes/<id>/review/`): по нему
|
**Отчёт триажа сохрани вместе с change (`openspec/changes/<id>/review/`) — это
|
||||||
потом видно, что было найдено и что из этого осталось незаведённым.
|
обязательно, а не «если удобно».** По нему потом видно, что было найдено и что из
|
||||||
|
этого осталось в урожае. И это единственный **независимый** артефакт о составе
|
||||||
|
прогона: под оркестратором `task-batch` именно по нему сверяют полноту ревью
|
||||||
|
ветки, а не по твоей прозе — она написана тем же, кто мог проход и пропустить.
|
||||||
|
|
||||||
### 8. Архивировать — `opsx:archive`
|
### 8. Архивировать — `opsx:archive`
|
||||||
|
|
||||||
Вызови Skill `opsx:archive`: change уезжает в архив, дельты вливаются в
|
Вызови Skill `opsx:archive`: change уезжает в архив, дельты вливаются в
|
||||||
актуальные спеки.
|
актуальные спеки.
|
||||||
|
|
||||||
### 9. Синк документации и закрытие задачи
|
### 9. Синк документации
|
||||||
|
|
||||||
Ревью выполненного — **до** закрытия. Затем:
|
Ревью выполненного — до этого шага. Затем:
|
||||||
|
|
||||||
- суть переехавшего решения — в документацию проекта (архитектура, журнал
|
- суть переехавшего решения — в документацию проекта (архитектура, журнал
|
||||||
решений), если её там ещё нет;
|
решений), если её там ещё нет;
|
||||||
- менялась схема — её описание обновлено тем же change;
|
- менялась схема — её описание обновлено тем же change;
|
||||||
- новое, узнанное о внешнем формате или о данных, — в тот файл проекта, который
|
- новое, узнанное о внешнем формате или о данных, — в тот файл проекта, который
|
||||||
это накапливает; такой файл обычно ценнее кода;
|
это накапливает; такой файл обычно ценнее кода;
|
||||||
- **задача закрывается процедурой проекта** — своей у пайплайна нет. Есть скилл
|
- воспроизведённый дефект (свой или чужой) — в раздел `## Прецеденты` брифа:
|
||||||
или скрипт беклога — вызови его; нет — скажи в докладе, что задача сделана и
|
класс, симптом, чем воспроизведён, чем закончилось. Это единственный артефакт,
|
||||||
закрытие остаётся за вызывающим. Не выдумывай формат чужого индекса.
|
который делает следующее ревью умнее.
|
||||||
|
|
||||||
|
**Задачу пайплайн не закрывает.** Записи учёта — индекс, статус, спринт — он не
|
||||||
|
трогает вовсе: закрытие происходит **после приёмки** и делается владельцем
|
||||||
|
спринта, а пайплайн на этом шаге стоит до коммита и до всякой приёмки. Скриптов
|
||||||
|
и скиллов учёта не зови — их у тебя и нет: пути между плагинами не разрешаются, и
|
||||||
|
моста здесь намеренно не проложено. Твоё дело — назвать исход в докладе.
|
||||||
|
|
||||||
### 10. Коммит
|
### 10. Коммит
|
||||||
|
|
||||||
@@ -242,13 +299,17 @@ description: Автономно проводит одну задачу чере
|
|||||||
сделано», тело списком 1–3 пункта, без трейлеров). Одна задача — один осмысленный
|
сделано», тело списком 1–3 пункта, без трейлеров). Одна задача — один осмысленный
|
||||||
коммит.
|
коммит.
|
||||||
|
|
||||||
Готово — доложи кратко:
|
Готово — доложи кратко. Доклад и есть выход пайплайна: **задача остаётся
|
||||||
|
открытой**, её закрывает владелец спринта после приёмки.
|
||||||
|
|
||||||
- **исход** задачи одним из трёх слов и, если не «сделана», чем ограничен
|
- **исход** задачи одним из трёх слов и, если не «сделана», чем ограничен
|
||||||
результат;
|
результат;
|
||||||
- что сделано, какие вопросы записаны и куда;
|
- что сделано, какие вопросы записаны и куда;
|
||||||
- ссылка на архивный change;
|
- ссылка на архивный change и хеш коммита;
|
||||||
- исход по каждому критерию приёмки, если они были;
|
- по каждому критерию приёмки, если они были: **оракул и наблюдаемый исход** —
|
||||||
|
это доклад приёмщику, а не отметка «принято»;
|
||||||
|
- **`Урожай`** — отложенные находки списком (формулировка, оракул, провенанс).
|
||||||
|
Задачи из него заводит тот, кто ведёт задачи проекта;
|
||||||
- **одна строка границ покрытия**: какой профиль и режим гонялись, какие проходы
|
- **одна строка границ покрытия**: какой профиль и режим гонялись, какие проходы
|
||||||
не запускались и что проверить было невозможно. Доклад без неё сообщает
|
не запускались и что проверить было невозможно. Доклад без неё сообщает
|
||||||
«проверено», не сообщая, что именно.
|
«проверено», не сообщая, что именно.
|
||||||
@@ -270,5 +331,4 @@ description: Автономно проводит одну задачу чере
|
|||||||
- **Занизить профиль ревью или пропустить проход — самый дешёвый способ
|
- **Занизить профиль ревью или пропустить проход — самый дешёвый способ
|
||||||
«ускориться», и он же самый дорогой по последствиям.** Защита одна: профиль
|
«ускориться», и он же самый дорогой по последствиям.** Защита одна: профиль
|
||||||
выбирается по факту изменения, состав сверяется поимённо, а непущенное
|
выбирается по факту изменения, состав сверяется поимённо, а непущенное
|
||||||
называется в отчёте. Пропуск, названный строкой, стоит строки; пропуск молчащий
|
называется в отчёте строкой.
|
||||||
стоил семи находок.
|
|
||||||
|
|||||||
Reference in New Issue
Block a user