добавлены плагины av-dev-tasks и av-dev-pipeline
Пара плагинов с намеренно проведённой границей: av-dev-tasks отвечает за то, что делаем и в каком порядке, av-dev-pipeline — за то, как ведём одну задачу. Зависимости между ними нет: управление задачами работает и с ручным исполнением, пайплайн — на проекте с любым учётом задач. - av-dev-tasks — преемник av-dev-backlog: цели вместо приоритетов, спринт под одну цель с заморозкой набора, различение вопроса и блокера, каденция «вопросы — разбор — переоценка — набор». Раскладка docs/tasks с items/, PLAN.md, BACKLOG.md, SPRINT.md, REJECTED.md; проверенное из av-dev-backlog перенесено, не переписано. - av-dev-pipeline — вынос того, что лежало копиями в healthlog и jellybit (3628 строк) и уже разошлось: цикл SDD, конвейер ревью с обязательным триажем, прогон нескольких задач разом. Проектная специфика вынесена в файл-бриф, charter'ы несут метод. Коммит фиксирует состояние на момент ревью: три прохода нашли блокирующие дефекты (нет шага, заводящего бриф; git rebase на занятой worktree ветке; sprint drop пишет наполовину) — они чинятся следующими коммитами. Сохранено как база, от которой видно правки.
This commit is contained in:
@@ -0,0 +1,413 @@
|
||||
---
|
||||
name: review-pipeline
|
||||
description: Конвейер ревью изменения — детерминированный гейт, сверка с дельта-спеками в обе стороны, враждебные постановки и эксплуатационный постмортем, независимая реализация по триггеру, архитектура и обязательный триаж. Проходы гонятся последовательно; параллельно — только по явной просьбе и с явно названным набором. Проектная специфика приходит из файла-брифа. Вызывается из task-pipeline (чекпоинты ревью), из task-batch (финальная сверка) и отдельно — профилем design на предложении ДО кода.
|
||||
---
|
||||
|
||||
# Конвейер ревью
|
||||
|
||||
Готовит ревью — **не заменяет его**. Потребитель отчёта — оркестратор, который
|
||||
чинит код; человек читает только сводку, развилки и границы покрытия.
|
||||
|
||||
## Три правила, из которых всё следует
|
||||
|
||||
Если ситуация не покрыта инструкцией — решай по ним.
|
||||
|
||||
1. **Recall чек-листа равен длине чек-листа.** Проход, устроенный как «проверь
|
||||
пункты 1..N», найдёт ровно перечисленное. Всё неявное — идиомы, форма
|
||||
решения, «так не делают» — неперечислимо по определению: перечислимое уже
|
||||
стало бы конвенцией. Отсюда деление проходов на **applicative** (применяют
|
||||
заданный критерий) и **generative** (сперва порождают критерий или
|
||||
альтернативу, потом сравнивают). Расширять чек-листы бесполезно; неявный слой
|
||||
достают только generative-проходы.
|
||||
2. **Ценность верификатора = наличие внешнего оракула × декорреляция с
|
||||
автором**, а не число ролей. Под всеми ролями одна модель с одними
|
||||
априорными, вход у всех общий: седьмая роль почти не добавляет recall, но
|
||||
линейно удорожает триаж. Иерархия надёжности: детерминированный инструмент >
|
||||
агент, который его **запускает** и интерпретирует вывод > агент с чистым
|
||||
мнением. Максимум работы переносим вниз.
|
||||
3. **Отчёт без границ покрытия хуже отсутствия отчёта.** «Критичных проблем не
|
||||
обнаружено» потребляет ощущение проверенности, ничего не гарантируя. Секция
|
||||
границ покрытия обязательна и не сокращается — в том числе в докладе человеку.
|
||||
|
||||
## Что конвейер защищает — приходит из брифа
|
||||
|
||||
Проходы общие, а нарушать нельзя проектное. Список инвариантов, команду гейта,
|
||||
объёмы и модель угроз конвейер **не знает** — он читает их в брифе проекта:
|
||||
[references/project-brief.md](references/project-brief.md) описывает контракт,
|
||||
[references/brief-template.md](references/brief-template.md) — образец
|
||||
заполнения.
|
||||
|
||||
Разреши путь к брифу один раз, в начале прогона: путь из задания →
|
||||
`docs/review-brief.md` → `.claude/review-brief.md`. Дальше передавай готовым.
|
||||
|
||||
**Брифа нет — прогон идёт в деградированном режиме**: `critical` по основанию
|
||||
«нарушен инвариант проекта» никем не присваивается, числа объёма не
|
||||
используются, и в границы покрытия уезжает строка «брифа проекта нет». Это дыра
|
||||
покрытия, а не нейтральное умолчание.
|
||||
|
||||
## Что получает каждый проход
|
||||
|
||||
Задание любому проходу состоит из шести вещей, и первые две без брифа
|
||||
бессмысленны:
|
||||
|
||||
- **бриф** — путь;
|
||||
- **контракт находок** — путь к
|
||||
[references/finding-contract.md](references/finding-contract.md) (в
|
||||
установленном плагине — `${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/`);
|
||||
- **изменение** — идентификатор change и путь к его дельта-спекам;
|
||||
- **база диффа**;
|
||||
- **профиль и режим** прогона — чтобы проход знал, что писать в границы покрытия;
|
||||
- **сужение**, если оно есть: конкретный узел, конкретная capability.
|
||||
|
||||
Чего проход **не** получает ни в каком режиме — выводов других проходов. См.
|
||||
«Режим запуска».
|
||||
|
||||
## Модель по проходу
|
||||
|
||||
Следует из правила 2: чем больше работы делает детерминированный инструмент,
|
||||
тем дешевле может быть модель; чем больше проход **порождает** критерий, тем
|
||||
дороже. Модель задана во frontmatter каждого агента, менять её здесь не нужно.
|
||||
|
||||
| Модель | Проходы | Почему |
|
||||
|---|---|---|
|
||||
| `sonnet` | gate, code, ops | вход структурный, критерий записан заранее |
|
||||
| `opus` | specs, adversary, rubric, reimpl | суждение без опоры на инструмент |
|
||||
| `fable` | triage, architecture | ошибка распространяется дальше самой находки |
|
||||
|
||||
**Самая дорогая модель — только двум проходам, и это калибровка, а не
|
||||
осторожность.** Замер: на первом же прогоне конвейера самые ценные находки дали
|
||||
`opus`-проходы — сверка спек дала 13 находок с оракулами, а проход про
|
||||
идиоматичность (впоследствии упразднённый) — три эксперимента против драйвера БД
|
||||
с воспроизведёнными числами. Разницы в пользу более дорогой модели на
|
||||
опиниативных проходах не обнаружилось — значит платить за неё там не за что.
|
||||
|
||||
Двое, у кого она остаётся, отобраны по одному признаку: **их ошибка
|
||||
распространяется дальше собственной находки.**
|
||||
|
||||
- `triage` — через него проходит всё, что оркестратор реализует **молча**:
|
||||
ложноположительная находка становится кодом, потерянный `critical` — дефектом.
|
||||
Ошибка триажа дороже ошибки любого отдельного прохода.
|
||||
- `architecture` — запускается редко (только `deep` и `design`), потолок в
|
||||
3 находки делает его дешёвым по выходу, а находка на предложении стоит абзаца
|
||||
против переписывания на готовом коде. Дёшево × высокое плечо.
|
||||
|
||||
`reimpl` намеренно **не** в этом списке, хотя он самый ценный из generative: его
|
||||
стоимость определяется объёмом вывода (он пишет реализацию целиком), так что
|
||||
дорогая модель множит самый большой счёт. Ценность же его — в **независимости**
|
||||
взгляда, а не в мощности модели.
|
||||
|
||||
**Самая дешёвая модель не используется ни на одном проходе, и это не экономия
|
||||
наоборот.** Дешёвая модель на опиниативном проходе даёт правдоподобные находки,
|
||||
которые триаж обязан опровергать оракулом, — а это самая дорогая операция
|
||||
конвейера. Механизируемая же работа здесь вынесена **ниже** модели: гейт,
|
||||
покрытие диффа, карта проекта — это скрипты проекта, они стоят ноль токенов.
|
||||
Дешёвому проходу просто не осталось работы.
|
||||
|
||||
Экономия достигается не понижением модели, а **непуском прохода**: `quick` —
|
||||
четыре прохода, `deep` — семь-восемь. Правило выбора профиля и есть главный
|
||||
рычаг стоимости.
|
||||
|
||||
## Профили
|
||||
|
||||
| Профиль | Когда | Стадии | Проходов |
|
||||
|---|---|---|---|
|
||||
| `quick` | багфикс, локальная правка, доки | 0, 1, 5 | 4 |
|
||||
| `standard` | новая функциональность в существующем пакете | 0, 1, 2, 5 | 6 |
|
||||
| `deep` | новый пакет, изменение публичного контракта, миграция схемы, трогает инварианты брифа | 0, 1, 2, 3, 4, 5 | 7–8 |
|
||||
| `design` | **до кода**, на предложении | specs + rubric + architecture (см. ниже) | 3 |
|
||||
|
||||
**Состав сверяется по этой таблице до коммита.** Реестр из трёх-восьми пунктов
|
||||
проверяется взглядом — и это единственная защита от промаха, который уже
|
||||
случился: пропуск прохода **не отличим от прохода без находок** (гейт зелёный,
|
||||
спеки сошлись, отчёт выглядит полным), а заметить его мог бы только триаж,
|
||||
который сам заполняется тем, что ему подали. Отчёт обязан перечислять запущенные
|
||||
проходы **поимённо и с исходом**; непущенный идёт строкой «не запускался» в
|
||||
границы покрытия, а не отсутствует. Цена молчащего пропуска измерена: семь
|
||||
находок и отдельная задача на их дозакрытие.
|
||||
|
||||
Правило выбора профиля — **по факту изменения, не по ощущению важности**:
|
||||
|
||||
- есть миграция схемы, новый пакет, изменение публичного контракта (API,
|
||||
протокол, формат на диске) или трогается правило, определяющее идентичность и
|
||||
слияние данных → `deep`;
|
||||
- иначе меняется поведение, видимое снаружи (эндпоинт, форма ответа, код ответа,
|
||||
формат лога) → `standard`;
|
||||
- иначе → `quick`.
|
||||
|
||||
Что именно в этом проекте считается публичным контрактом и какие пути означают
|
||||
`deep` — раздел `## Триггеры` брифа. Он **уточняет** правило, а не отменяет его:
|
||||
если триггеров в брифе нет, работает список выше.
|
||||
|
||||
Профиль объявляется в отчёте. Понижение профиля — решение оркестратора, и оно
|
||||
попадает в границы покрытия строкой «профиль понижен до X, потому что …».
|
||||
|
||||
## Режим запуска: параллельно или последовательно
|
||||
|
||||
Профиль отвечает «какие проходы», режим — «как их запускать». Стадии всегда идут
|
||||
по порядку номеров; выбор касается только проходов **внутри** стадии.
|
||||
|
||||
| Режим | Как | Когда |
|
||||
|---|---|---|
|
||||
| **последовательно** (умолчание) | по одному, следующий стартует после отчёта предыдущего | всегда, пока не попросили иначе |
|
||||
| **параллельно** | названные проходы — одним сообщением | только по явной просьбе **и** с явно названным набором |
|
||||
|
||||
**Умолчание — последовательно, и его не надо обосновывать.** Обосновывается
|
||||
отступление.
|
||||
|
||||
**Параллельный режим включается при двух условиях сразу**, и второе так же
|
||||
обязательно, как первое:
|
||||
|
||||
1. **о нём попросили явно** — «гони параллельно», а не «сделай побыстрее»;
|
||||
2. **названо, что именно гнать параллельно** — поимённый набор проходов
|
||||
(«`specs` и `code` параллельно») или стадия целиком («стадию 1 параллельно»).
|
||||
|
||||
Просьба без набора — **не основание**: гоним последовательно и одной строкой
|
||||
говорим, что набор не был назван. Это не придирка к формулировке. Параллелить
|
||||
можно ровно то, что не мешает друг другу, а знание об этом лежит у того, кто
|
||||
просит: он видит, занята ли машина, и ждёт ли он от прогона замеров. Домысливать
|
||||
набор за него — значит принять решение, которое он оставил себе.
|
||||
|
||||
Почему умолчание именно такое:
|
||||
|
||||
- **Замеры.** Проходы `adversary` и `ops` доказывают находки числами: время
|
||||
удержания блокировки, пик кучи, рост файлов журнала, длительность транзакции.
|
||||
Два меряющих прохода на одной машине соревнуются за диск, CPU и за саму СУБД и
|
||||
выдают числа, которые не воспроизведутся. Это не гипотеза: находки, ради
|
||||
которых правило записано, опираются ровно на такие замеры (5.019 с удержания
|
||||
блокировки при таймауте 5000 мс, пик 768 МиБ на теле 40 МиБ, 7 МБ/с роста
|
||||
журнала, 1492 тика из 5502). Число, снятое под конкурентную нагрузку от
|
||||
соседнего прохода, — это находка с испорченным оракулом, а её опровержение
|
||||
стоит дороже всего выигрыша от параллельности.
|
||||
- **Машина одна.** Рядом идёт задача, поднят сервис, гоняется гейт или дорогая
|
||||
проверка проекта.
|
||||
- **Ранний выход** возможен только при последовательном прогоне (см. ниже).
|
||||
- **Разбор самого конвейера.** Когда выясняется, почему проход чего-то не нашёл,
|
||||
порядок и изоляция важнее скорости.
|
||||
|
||||
Если параллельный режим всё же включён, в границы покрытия идёт строка: какие
|
||||
проходы шли разом и что замеры, снятые в этом прогоне, как оракул слабее.
|
||||
|
||||
**Чего режим не меняет — и это не подлежит обсуждению.** Проход **не видит**
|
||||
находок других проходов ни в каком режиме. «Последовательно» значит «по
|
||||
очереди», а не «следующий читает предыдущего». Вся ценность конвейера держится
|
||||
на декорреляции: под всеми ролями одна модель с одними априорными, и стоит
|
||||
показать ей чужой вывод — она согласится. Согласие нескольких проходов и так не
|
||||
повышает `confidence` (см. «Честный предел»); согласие **наведённое** ещё и
|
||||
маскируется под независимое подтверждение. Единственный, кто видит всё, — триаж,
|
||||
и это его работа.
|
||||
|
||||
**Ранний выход** (последовательный режим делает его возможным — это его побочная
|
||||
выгода, а не повод его выбирать). Допустимо остановить прогон, не докатив
|
||||
остаток, ровно в одном случае: находка требует **переделки формы** изменения, и
|
||||
остальные проходы будут смотреть на код, которого через час не станет. Тогда:
|
||||
|
||||
- прогон останавливается, находка чинится, конвейер запускается **заново с
|
||||
нулевой стадии** — а не «доезжает» остатком по старому коду;
|
||||
- незапущенные проходы идут в границы покрытия строкой «не запускался: прогон
|
||||
остановлен на <проход> из-за <находка>», поимённо;
|
||||
- триаж запускается только на полном прогоне. Отчёт триажа по половине проходов —
|
||||
ровно тот случай, который уже стоил семи находок: он выглядит полным, потому
|
||||
что агрегирует всё, что ему подали.
|
||||
|
||||
Ранний выход по находке, которая чинится в пределах существующей формы
|
||||
(`Действие: инлайн`), **не делается**: дешевле дособрать все находки и починить
|
||||
пачкой, чем гонять конвейер дважды.
|
||||
|
||||
Режим объявляется в отчёте наравне с профилем, и если он **параллельный** — с
|
||||
причиной и составом. Последовательный объявляется одним словом.
|
||||
|
||||
## Стадия 0 — Gate (обязательна во всех профилях)
|
||||
|
||||
Агент `review-gate`. Запускает команду гейта из раздела `## Гейт` брифа и
|
||||
интерпретирует вывод.
|
||||
|
||||
**Пока гейт красный — опиниативные проходы не запускаются.** Оркестратор чинит и
|
||||
перезапускает гейт. Исключение одно: отказ, унаследованный от базовой ветки
|
||||
(гейт проверяет это прогоном на базе) — тогда он фиксируется находкой и не
|
||||
блокирует.
|
||||
|
||||
Гейт возвращает не только «зелено/красно», но и находки класса **отсутствующая
|
||||
верификация**: изменённые строки без покрытия, конкурентность без теста с
|
||||
параллельным доступом, флаки-тест (не ниже `major`), недоступный инструмент.
|
||||
|
||||
Шаги выбираются по изменённым файлам: правка документации не гоняет тесты,
|
||||
линтеры и детектор гонок. Пропуск при этом не молчит — он виден в сводке с
|
||||
причиной и уезжает в границы покрытия, как и любой другой `SKIP`.
|
||||
|
||||
Шаги, которые красят гейт безусловно, перечислены в брифе с причиной. Проходу
|
||||
запрещено списывать такой отказ в мелочь.
|
||||
|
||||
## Стадия 1 — Conformance (обязательна во всех профилях)
|
||||
|
||||
Два applicative-прохода: оба применяют **записанный** критерий, оба дешёвые.
|
||||
Замеров они не делают и потому безобиднее прочих, если параллельный режим
|
||||
попросят с их именами; сами по себе идут по очереди, как и все.
|
||||
|
||||
- `review-specs` — критерий взят из **дельта-спек предлагаемого изменения**, а не
|
||||
из proposal, сообщения коммита или описания задачи. Сверка двунаправленная;
|
||||
направление `code → spec` важнее.
|
||||
- `review-code` — критерий взят из файла конвенций проекта (раздел `## Карта`
|
||||
брифа), и только та его часть, которая **не выражается правилом**:
|
||||
механизируемое уже проверила стадия 0. Что именно механизировано, тот же раздел
|
||||
брифа перечисляет — повторять это проходом вредно.
|
||||
|
||||
Recall обоих равен длине их источника — это и есть предел applicative-проходов,
|
||||
ради которого существует стадия 2.
|
||||
|
||||
## Стадия 2 — Adversarial и operational (`standard`, `deep`)
|
||||
|
||||
Два прохода:
|
||||
|
||||
- `review-adversary` — находка есть **построенный путь**, а не свойство;
|
||||
- `review-ops` — постмортем от симптома у владельца сервиса к строке кода.
|
||||
|
||||
**Эту пару параллелить не стоит даже по просьбе — переспроси.** Оба доказывают
|
||||
находки замером, и оба меряют одно и то же железо. Запущенные разом, они портят
|
||||
числа друг другу, а испорченный оракул хуже отсутствующего: находка выглядит
|
||||
доказанной. Если их всё же назвали в параллельном наборе — выполняй, но скажи в
|
||||
границах покрытия, что числа этого прогона сняты под соседней нагрузкой.
|
||||
|
||||
**Эта стадия зарабатывает больше всех остальных вместе, и потому стоит в
|
||||
`standard`, а не только в `deep`.** Измерено на пяти задачах подряд: враждебный
|
||||
проход дал пять из семи выживших находок дозапуска (включая обе верхние);
|
||||
эксплуатационный — единственный, кто нашёл, что откат бинаря поверх новой схемы
|
||||
стартует молча. Оба несут внешний оракул по построению: один обязан путь
|
||||
**прогнать**, второй смотрит ось времени и эксплуатации, которую не смотрит
|
||||
никто другой.
|
||||
|
||||
Материал обоим даёт бриф: `## Модель угроз` — враждебному, `## Прод и поток` —
|
||||
эксплуатационному. Без этих разделов стадия вырождается в общие места.
|
||||
|
||||
## Стадия 3 — Independent reimplementation (`deep`, по триггеру)
|
||||
|
||||
- `review-reimpl` — пишет свою реализацию, не открывая существующую, затем
|
||||
диффит по решениям. **Запускается по триггеру, а не всегда:** изменение вводит
|
||||
новое правило идентичности, слияния или разбора (проектная формулировка
|
||||
триггера — в разделе `## Триггеры` брифа). Это самый дорогой проход конвейера
|
||||
(его счёт определяется объёмом вывода — он пишет реализацию целиком), а вне
|
||||
этого триггера независимый взгляд в значительной мере уже дал профиль `design`:
|
||||
код писался под его находки. Триггер выбран по факту: единственный раз, когда
|
||||
триаж назвал отсутствие `reimpl` дырой покрытия, — это была задача с новым
|
||||
правилом слияния сущностей.
|
||||
|
||||
## Стадия 4 — Global (`deep`, `design`)
|
||||
|
||||
Агент `review-architecture`. Получает **вход шире диффа**: дерево пакетов с
|
||||
назначением, граф внутренних зависимостей, инвентарь существующих концепций.
|
||||
Команду, которая это готовит, даёт раздел `## Команды` брифа; нет команды —
|
||||
проход собирает карту сам и говорит об этом в границах покрытия.
|
||||
|
||||
Главный вопрос — концептуальная целостность и **второй способ** делать то, что
|
||||
уже делается. Он же и оправдывает проход: на задаче про пересборку архитектурный
|
||||
проход нашёл, что новый код был **вторым проигрывателем журнала** со своим
|
||||
порядком. Второй обязательный вопрос — **что опытный человек отсюда удалил бы**:
|
||||
слой с единственной реализацией, интерфейс ради мока, незапрошенная
|
||||
конфигурируемость, подстраховка поверх подстраховки. Потолок — 3 находки плюс
|
||||
секция «дешевле переделать до мерджа».
|
||||
|
||||
## Стадия 5 — Triage (обязательна)
|
||||
|
||||
Агент `review-triage`. Единственный, кто агрегирует. Получает сырые выводы всех
|
||||
проходов, `git diff`, профиль, режим и **список запущенных проходов**; возвращает
|
||||
финальный отчёт.
|
||||
|
||||
Без триажа проходы дают порядка сорока замечаний при единицах существенных.
|
||||
Потребитель здесь — оркестратор, который **молча реализует** всё, что прочитал:
|
||||
цена нетриажированного отчёта — не потерянное время человека, а разросшийся от
|
||||
вкусовщины код.
|
||||
|
||||
Порядок: дедупликация по причине → оракул для всего `critical`/`major` →
|
||||
понижение неподтверждённого до гипотезы → отсев вкусовщины → ранжирование по
|
||||
ущербу × вероятности → потолок 7 пунктов в основном списке.
|
||||
|
||||
## Профиль `design` — до кода
|
||||
|
||||
Запускается на шаге ревью спек (шаг 4 скилла `task-pipeline`), когда change уже
|
||||
имеет `proposal.md` и дельта-спеки, но кода ещё нет. Состав:
|
||||
|
||||
1. `review-specs` в режиме «дизайн ДО кода»;
|
||||
2. `review-rubric`, фаза 1 без фазы 2: рубрика на задуманный узел становится
|
||||
приёмочными критериями и уезжает в `tasks.md`;
|
||||
3. `review-architecture` на предложении: вводит ли change новое понятие, можно ли
|
||||
выразить существующими — **включая конструкции стандартной библиотеки**, — не
|
||||
появляется ли второй способ. Вопрос «не изобретаем ли то, что уже есть в
|
||||
библиотеке» живёт здесь;
|
||||
4. вопрос автору дизайна: **«предложи три формы решения и назови компромисс
|
||||
каждой»** — если ответ показывает, что рассматривалась одна, это находка.
|
||||
|
||||
Смысл профиля: архитектурная находка на готовом коде стоит переписывания и
|
||||
поэтому игнорируется; та же находка на предложении стоит абзаца обсуждения.
|
||||
|
||||
`rubric` живёт **только** в этом профиле. Судить код по критерию, под который он
|
||||
писался, — корреляция по построению; те же 8–14 свойств уже лежат приёмочными
|
||||
критериями в `tasks.md`.
|
||||
|
||||
## Контракт находок
|
||||
|
||||
Единый для всех проходов — [references/finding-contract.md](references/finding-contract.md).
|
||||
Коротко: заголовок через **последствие**, обязательные поля `Файл`, `Severity`,
|
||||
`Confidence`, `Оракул`, `Последствие`, `Предложение`, `Найдено проходом`.
|
||||
`critical` без оракула или построенного пути не существует. Находка без поля
|
||||
«Последствие» не выводится вовсе.
|
||||
|
||||
Каждый проход завершает вывод блоком `## Coverage of this pass`.
|
||||
|
||||
## Что происходит с находками дальше
|
||||
|
||||
- Оркестратор чинит помеченное `Действие: инлайн` и **не логирует мелочь**.
|
||||
- `Действие: развилка` — вопросом с вариантами и ценой каждого туда, где проект
|
||||
держит вопросы (это знает вызвавший пайплайн, а не конвейер). Оркестратор не
|
||||
останавливается: он урезает изменение до остатка и доводит его.
|
||||
- Находка не для этого мерджа, но реальная (отложенный `major`, развилка,
|
||||
решённая «потом») — не теряется: заводится задачей средствами проекта, с
|
||||
оракулом и провенансом в теле. Мелочь класса `nit` — пачкой, а не записью на
|
||||
находку.
|
||||
- `Promote candidates` — по процедуре [references/promote.md](references/promote.md):
|
||||
находка → конвенция → правило линтера → **удаление из конвенций и из брифа**.
|
||||
Третий шаг обязателен.
|
||||
- Дефект, проскочивший ревью и всплывший позже, идёт в журнал проекта
|
||||
([references/review-journal.md](references/review-journal.md)) — сразу, не
|
||||
ретроспективно: теряется именно причина непоймания.
|
||||
- **Отчёт триажа сохраняется вместе с изменением** (например, в
|
||||
`openspec/changes/<id>/review/`). Он единственное, по чему потом видно, что
|
||||
было найдено и что из этого не заведено: нулевой урожай при непустом отчёте
|
||||
виден сразу.
|
||||
|
||||
## Честный предел
|
||||
|
||||
Модель воспроизводит медиану публичного кода, смещённую к популярному и
|
||||
туториальному: отсюда тяга к интерфейсам ради интерфейсов, лишним мокам и
|
||||
конфигурируемости, которую никто не просил. **«Идиоматично» и «распространено» —
|
||||
разные вещи**; проходы обязаны различать их и опираться на поимённое положение
|
||||
гайда, а не на ощущение частотности.
|
||||
|
||||
Согласие нескольких проходов — **не подтверждение**: это один источник,
|
||||
высказавшийся несколько раз. Совпадение повышает приоритет, но не `confidence`.
|
||||
|
||||
Что недоступно **этому** проекту принципиально — перечисляет раздел
|
||||
`## Недоступно проверке` брифа, и он целиком уезжает в границы покрытия.
|
||||
Независимо от проекта недоступно:
|
||||
|
||||
- поведение внешних систем в их будущих версиях;
|
||||
- реальный профиль нагрузки и то, что на самом деле лежит в данных;
|
||||
- завязка внешних потребителей на текущую форму ответа;
|
||||
- суждение «этой функциональности не должно существовать».
|
||||
|
||||
Отдельно и честно: **поимённая сверка с положениями стайлгайдов языка не
|
||||
задаётся ни одним проходом.** Проход про идиоматичность упразднён, его способные
|
||||
части переселены (эксперимент против поведения библиотеки и драйвера — в `ops`,
|
||||
вопрос 8; «не изобретаем ли то, что уже есть в библиотеке» — в `architecture`,
|
||||
вопрос 1), но различение «идиоматично против распространено» теперь не спрашивает
|
||||
никто. Класс обратимый — портит форму кода, не данные, — и его надо признавать в
|
||||
границах покрытия, а не считать проверенным.
|
||||
|
||||
Это и есть причина, по которой конвейер готовит ревью, а не заменяет его.
|
||||
|
||||
## Ссылки
|
||||
|
||||
- [references/project-brief.md](references/project-brief.md) — контракт брифа проекта.
|
||||
- [references/brief-template.md](references/brief-template.md) — шаблон брифа.
|
||||
- [references/finding-contract.md](references/finding-contract.md) — контракт находок.
|
||||
- [references/promote.md](references/promote.md) — промоут находка → конвенция → правило → удаление.
|
||||
- [references/calibration.md](references/calibration.md) — калибровка инъекцией, вердикты keep/retune/drop.
|
||||
- [references/review-journal.md](references/review-journal.md) — журнал проскочивших дефектов.
|
||||
@@ -0,0 +1,179 @@
|
||||
# Шаблон брифа проекта
|
||||
|
||||
Скопируй в `docs/review-brief.md` и заполни. Контракт разделов — в
|
||||
[project-brief.md](project-brief.md); здесь только образец заполнения.
|
||||
|
||||
Курсивом даны пояснения — их из готового брифа убирают. Примеры взяты из двух
|
||||
разных проектов (коллектор данных с непрерывным потоком и связующий сервис вокруг
|
||||
внешних демонов), чтобы было видно, как один и тот же раздел выглядит при разной
|
||||
природе проекта.
|
||||
|
||||
---
|
||||
|
||||
## Проект
|
||||
|
||||
*Абзац: что делает — и чего не делает.*
|
||||
|
||||
> Коллектор выгрузок с телефона. Принимает доставки, хранит их и отдаёт другим
|
||||
> сервисам. Это **хранилище, а не аналитика**: принять, дедуплицировать,
|
||||
> сохранить, отдать. Не переименовывать поля источника, не интерпретировать
|
||||
> значения; свёртка считается только в ответе на запрос.
|
||||
|
||||
> Связующий сервис между качалкой и медиасервером: принимает задание, качает,
|
||||
> распознаёт содержимое, раскладывает файлы ссылками. **Не медиатека и не
|
||||
> плеер** — ничего не хранит сверх метаданных о раскладке.
|
||||
|
||||
## Инварианты
|
||||
|
||||
*Проверяемое свойство + последствие + severity по умолчанию. Цитируются
|
||||
формулировкой.*
|
||||
|
||||
- **Точка сохраняется дословно.** Незнакомое поле не отбрасывается, число не
|
||||
округляется при записи. Нарушение — необратимая потеря: сырой архив живёт
|
||||
14 дней, дальше истина только в свёртке. По умолчанию `critical`.
|
||||
- **Источник неприкосновенен.** Только `mkdir`/`link(2)`/`unlink` собственных
|
||||
ссылок; файлы под каталогом загрузок не трогаются никогда. Нарушение —
|
||||
повреждение чужих данных, необратимое. По умолчанию `critical`.
|
||||
- **Сохранили — значит приняли.** Код ответа отражает доставку, а не разбор:
|
||||
непонятое содержимое — `200`, тело уже на диске. Нарушение стоит доставки,
|
||||
которую отправитель не повторит. По умолчанию `critical`.
|
||||
- **Секреты и данные пользователя не в логах.** Тело запроса — только на `DEBUG`
|
||||
и с обрезкой. По умолчанию `critical`.
|
||||
- **Агрегации при записи нет.** Нарушение искажает историю молча и
|
||||
диагностируется только сверкой с внешним источником, то есть месяцами позже.
|
||||
По умолчанию `major`, `critical` — если испорченное невосстановимо.
|
||||
|
||||
## Гейт
|
||||
|
||||
- **Команда:** `task gate BASE=<база>`; база по умолчанию —
|
||||
`git merge-base HEAD master`, на `master` — `HEAD~1`.
|
||||
- **Логи шагов:** `tmp/gate/<шаг>.log`. Сводка печатает `OK`/`FAIL`/`WARN`/`SKIP`;
|
||||
краснит гейт только `FAIL`.
|
||||
- **Шаги:** сборка, `vet`, линтеры, форматирование, тесты, повторный прогон на
|
||||
флаки, `-race`, покрытие изменённых строк, накат миграций с нуля, поиск
|
||||
секретов, `govulncheck`.
|
||||
- **Красят безусловно** *(перечислить с причиной — это главная часть раздела)*:
|
||||
- `no-user-data` — файл из каталога данных попал под контроль версий: убрать
|
||||
обычным коммитом уже нельзя;
|
||||
- `config-samples` — структура конфига изменилась, а образец нет: забытое поле
|
||||
обнаруживается не тестом, а тем, что через полгода о нём никто не знает;
|
||||
- `migrations` — миграции не накатываются с нуля: восстановление перестаёт
|
||||
работать ровно тогда, когда оно нужно;
|
||||
- `er-schema` — миграция тронута, а схема в документации не обновлена.
|
||||
- **Чего в гейте намеренно нет:** прогон на живом корпусе (`task verify:archive`)
|
||||
— минута работы и данные, которых нет ни на какой другой машине. У этой
|
||||
проверки краснота не видна никому до следующей задачи, которая до неё
|
||||
дотянется, — говори об этом в границах покрытия.
|
||||
|
||||
## Команды
|
||||
|
||||
- Карта проекта для архитектурного прохода: `task review:context > tmp/review-context.md`
|
||||
- Поднять изменение вживую: `task restart`, логи — `task logs`
|
||||
- Тесты и линт: `task test`, `task lint`
|
||||
- Дорогое, вручную: `task verify:archive` (минута, живые данные)
|
||||
- **Запускать запрещено:** ничего, что пишет в `./data`, в рабочую БД и в боевой
|
||||
каталог архива. Замеры — только на копиях в `./tmp`.
|
||||
|
||||
## Прод и поток
|
||||
|
||||
- **Где:** один статический бинарь в контейнере на домашнем сервере, перед ним
|
||||
обратный прокси с TLS, SQLite на диске. Ни оркестратора, ни реплик, ни дежурной
|
||||
смены.
|
||||
- **Внешние зависимости и как каждая отказывает:** прокси — рвёт соединение на
|
||||
длинном теле; диск — заполняется и тормозит; СУБД — отдаёт «занято» под
|
||||
параллельной записью; приложение-источник на телефоне — молча перестаёт слать.
|
||||
*(В другом проекте здесь были бы качалка, медиасервер, LLM и база метаданных, и
|
||||
каждая — со своим «отвечает медленно», а не только «упала».)*
|
||||
- **Кто заметит отказ:** один пользователь-владелец, в лучшем случае вечером, а
|
||||
скорее не заметит вовсе.
|
||||
- **Характер потока:** телефон шлёт непрерывно и молча; обратной связи у
|
||||
отправителя нет, об отказах он не сообщает, расписание плавает. Тихо
|
||||
сломавшаяся доставка — главный эксплуатационный риск.
|
||||
- **Числа (с провенансом):** нижний слой — порядка 135 тыс. точек в сутки
|
||||
(замер, `docs/local-research.md`); тела доходили до 42 МБ (там же); запись —
|
||||
read-modify-write под конкурентными доставками (`docs/architecture.md`).
|
||||
- **Обратимость:** падение сервиса обратимо — отправитель дошлёт широким
|
||||
проходом. Потеря или порча точки необратима. Поэтому тихая порча весит больше,
|
||||
чем «сервис вернул 500».
|
||||
|
||||
## Модель угроз
|
||||
|
||||
- **Недоверенное:** тело доставки целиком (имена метрик, единицы, формы точек,
|
||||
метки времени, глубина вложенности, размер); заголовки доставки, часть которых
|
||||
участвует в решениях; содержимое архива внешнего экспорта (имена файлов внутри
|
||||
zip мы не формировали); параметры читающего API.
|
||||
- **Из чего строятся пути и ключи:** файл сырого архива —
|
||||
`raw/ГГГГ/ММ/ДД/<ulid>.json.gz`, дата берётся из времени приёма, имя — из
|
||||
генератора идентификаторов; ключ записи — `метрика + слой + начало + конец`,
|
||||
источник в ключ не входит.
|
||||
- **Разграничение:** статические токены в `Authorization: Bearer`, раздельные на
|
||||
запись и на чтение; конфиг под `0600`.
|
||||
- **Что дороже:** данные пользователя дороже токена. Путь, по которому значение
|
||||
доезжает до лога выше `DEBUG`, до ответа с ошибкой или до `testdata` в git, —
|
||||
полноценная находка, а не замечание по гигиене.
|
||||
- **Вне модели:** злоумышленник в локальной сети; вредоносный оператор;
|
||||
компрометация поставщика данных; мультиарендность. Находки этих классов не
|
||||
выводятся — они никогда не будут исправлены.
|
||||
|
||||
## Карта
|
||||
|
||||
- Актуальные спеки: `openspec/specs/<capability>/spec.md`
|
||||
- Дельта-спеки изменения: `openspec/changes/<id>/specs/*/spec.md`
|
||||
- Конвенции прозой: `docs/conventions.md`. Механизировано и потому **не
|
||||
проверяется проходом по конвенциям**: форма логов, `fmt.Print*`/`os.Getenv`/
|
||||
`time.Now` мимо единых точек, сравнение ошибок, сторонние пакеты ошибок —
|
||||
всё это правила в `.golangci.yml`.
|
||||
- Архитектура и решения: `docs/architecture.md`
|
||||
- Журнал проскочивших дефектов: `docs/review-journal.md`
|
||||
- **Единые точки:** идентификаторы — `internal/ident`; время — `store.Now()`;
|
||||
разбор дат входного формата — один парсер в `internal/parse`; маппинг доменной
|
||||
ошибки в код ответа — одна точка в `internal/httpapi`; путь приёма — `ingest`,
|
||||
общий для HTTP и CLI. Инвентарь целиком выгружает `task review:context`.
|
||||
- **Нумерованные артефакты:** миграции — `internal/store/migrations/NNNN_*.sql`,
|
||||
номер монотонный, следующий свободный смотреть там же.
|
||||
- Задачи: `docs/backlog/` *(пайплайн только читает и сообщает исход)*
|
||||
- Реальные пакеты для тестов разбора: `internal/parse/testdata` — там данные
|
||||
пользователя с вычищенными токенами, наружу не копировать
|
||||
- Временное: `./tmp` (не системный `/tmp`)
|
||||
- **Не трогать:** `./data` — боевой архив и БД
|
||||
|
||||
## Типовые узлы
|
||||
|
||||
*Род узла + 3–5 специфичных проверяемых свойств.*
|
||||
|
||||
- **Разбор входного формата** — поведение на усечённом и враждебном входе,
|
||||
границы размера, отсутствие паники, детерминизм, судьба незнакомых полей.
|
||||
- **HTTP-обработчик приёма** — валидация формы конверта до записи, лимит тела и
|
||||
архивная бомба, что попадает в ответ, а что в лог, отсутствие доменной логики
|
||||
в транспорте.
|
||||
- **Обработчик читающего API** — предсказуемость размера ответа, поведение при
|
||||
пустом диапазоне, коды ответа на невозможный запрос.
|
||||
- **Репозиторий** — границы транзакции, конкурентная запись того же ключа,
|
||||
откуда берутся время и id, что возвращается при отсутствии записи,
|
||||
идемпотентность повторной записи.
|
||||
- **Файловое хранилище с ретеншеном** — атомарность записи, поведение при
|
||||
неполной записи и нехватке места, что удаляется и по какому критерию, можно ли
|
||||
удалить лишнее.
|
||||
- **CLI-команда пересборки** — идемпотентность повторного прогона, поведение при
|
||||
отмене на середине, что остаётся после падения, отчёт для человека.
|
||||
- **Клиент внешнего сервиса** — таймаут, протяжка `context`, поведение при
|
||||
«медленно» против «упало», ретраи и их граница.
|
||||
|
||||
## Триггеры
|
||||
|
||||
- `deep`: миграция в `internal/store/migrations/`, новый пакет `internal/*`,
|
||||
изменение контракта читающего API, правило слияния или вывод слоя.
|
||||
- «Видимое снаружи» (то есть `standard`): эндпоинт, форма ответа, код ответа
|
||||
приёма, формат лога.
|
||||
- `reimpl` запускается, когда изменение вводит **новое правило слияния,
|
||||
идентичности или разбора**.
|
||||
|
||||
## Недоступно проверке
|
||||
|
||||
- Поведение внешнего приложения-источника на следующем его обновлении.
|
||||
- Что реально лежит в системе-источнике: сверить можно только ручным экспортом,
|
||||
а он делается раз в 2–3 месяца.
|
||||
- Поведение таблицы под объёмом нескольких лет истории и реальный профиль
|
||||
нагрузки.
|
||||
- Завязка внешних потребителей на текущую форму ответа.
|
||||
- Суждение «этой функциональности не должно существовать».
|
||||
@@ -0,0 +1,85 @@
|
||||
# Калибровка проходов
|
||||
|
||||
Без измерения набор проходов растёт монотонно и вырождается в театр: каждый
|
||||
кажется полезным, потому что иногда что-то говорит. Калибровка отвечает на
|
||||
единственный вопрос — **ловит ли проход дефект своего класса**.
|
||||
|
||||
## Процедура (инъекция дефекта)
|
||||
|
||||
1. Взять **реальный коммит** из истории (`git log --oneline`), лучше
|
||||
архивированный change с непустым диффом.
|
||||
2. Внести в него **один** дефект того класса, который проход обязан ловить по
|
||||
своему charter'у. Дефект должен быть правдоподобным — таким, какой реально
|
||||
пишет модель, а не карикатурой (`panic("TODO")` не считается).
|
||||
3. Прогнать **только этот проход** на подготовленном диффе — **три раза**,
|
||||
каждый в чистом контексте.
|
||||
4. Зафиксировать: нашёл `n/3`, число находок всего, число ложных.
|
||||
5. Вердикт:
|
||||
|
||||
| Результат | Вердикт | Что делаем |
|
||||
|---|---|---|
|
||||
| нашёл 3/3 или 2/3, ложных немного | `keep` | ничего |
|
||||
| нашёл 1/3 или 0/3 | `retune` | правим charter — сужаем вход, убираем чек-лист, добавляем оракул |
|
||||
| `retune` уже был дважды подряд | `drop` | удаляем проход |
|
||||
| находит, но ложных больше трети от всех находок | `retune` | триаж съедает больше, чем экономит проход |
|
||||
|
||||
**`retune` не более двух раз подряд.** Проход, не находящий дефект своего класса
|
||||
в 2 из 3 прогонов после двух правок промпта, — это театр. Удалять, а не
|
||||
бесконечно править формулировки: каждая итерация правки промпта стоит дороже,
|
||||
чем отсутствие прохода.
|
||||
|
||||
**Существующий проход не удаляется без замера.** Сначала калибровка, потом
|
||||
решение — иначе удаляется то, что работало, а остаётся то, что громче. Обратный
|
||||
пример уже был: проход про идиоматичность стоял в списке на удаление как
|
||||
«вкусовщина», а замер показал, что он зарабатывает **экспериментами против
|
||||
поведения библиотеки и драйвера**, — и находка, воспроизведённая числом, отменила
|
||||
решение, принятое по ощущению.
|
||||
|
||||
## Состав проходов принадлежит плагину, а не проекту
|
||||
|
||||
Проходы общие. Проект не может удалить проход — он может **не звать** его, и
|
||||
тогда это идёт строкой «не запускался» в границы покрытия, как любой другой
|
||||
пропуск. Молча сузить состав нельзя: пропуск прохода не отличим от прохода без
|
||||
находок.
|
||||
|
||||
Отсюда два следствия:
|
||||
|
||||
- **правка charter'а — правка для всех проектов.** Прежде чем сужать
|
||||
формулировку под свою боль, проверь, не место ли ей в брифе: предмет проверки
|
||||
живёт там, метод — в charter'е;
|
||||
- **удаление прохода из плагина требует замера на двух проектах**, а не на одном:
|
||||
класс, не всплывший здесь, мог быть единственным работающим там.
|
||||
|
||||
## Пробы дефектов по проходам
|
||||
|
||||
Проба — заготовка инъекции. Список пополняется из журнала проскочивших дефектов
|
||||
(см. [review-journal.md](review-journal.md)): реальный проскочивший дефект —
|
||||
лучшая проба, какая вообще возможна, потому что синтетические смещены в сторону
|
||||
тех, которые уже умеешь придумывать.
|
||||
|
||||
| Проход | Класс дефекта для инъекции | Заготовка пробы |
|
||||
|---|---|---|
|
||||
| `review-gate` | отсутствующая верификация | убрать тест на изменённую ветку, оставить код рабочим |
|
||||
| `review-specs` | поведение вне спеки | добавить незаказанный фолбэк-дефолт на пустом входе |
|
||||
| `review-code` | нарушение прозаической конвенции | увести штатный отказ мимо единой точки трансляции ошибки |
|
||||
| `review-rubric` | нарушенное свойство узла | у клиента внешнего сервиса убрать таймаут и протяжку `context` |
|
||||
| `review-reimpl` | форма решения | размазать решение по трём слоям там, где хватало одной функции |
|
||||
| `review-architecture` | второй способ | завести вторую точку генерации id мимо единой |
|
||||
| `review-adversary` | построенный путь | принять внешний идентификатор без разбора до запроса в хранилище |
|
||||
| `review-ops` | деградация окружения | убрать обработку недоступности внешней зависимости в фоновом цикле |
|
||||
| `review-triage` | шум | подать 20 находок, из них 15 вкусовщина и 3 дубля — проверить потолок и дедуп |
|
||||
|
||||
Метрик сверх этого не заводим. Precision, корреляция между проходами, стоимость
|
||||
прогона в токенах — всё это красиво звучит и никем не считается вручную; набор
|
||||
показателей, который не собирают, создаёт впечатление измеряемости и тем вреден.
|
||||
Работает ровно один механизм: инъекция дефекта и вердикт. Если корреляция двух
|
||||
проходов действительно бросается в глаза — это видно по полю `Найдено проходом`
|
||||
в триажированных отчётах и без отдельной метрики.
|
||||
|
||||
## Когда калибровать
|
||||
|
||||
- при заведении нового прохода — **до** включения в профиль по умолчанию;
|
||||
- при правке charter'а существующего — иначе непонятно, правка помогла или нет;
|
||||
- при появлении записи в журнале проскочивших дефектов — калибруем тот проход,
|
||||
который должен был поймать;
|
||||
- планово — нет. Календарная калибровка ради галочки сама превращается в театр.
|
||||
@@ -0,0 +1,97 @@
|
||||
# Контракт находок
|
||||
|
||||
Единый формат для всех проходов конвейера ревью. Проход, нарушивший контракт,
|
||||
считается сломанным — триаж вправе выбросить его вывод целиком.
|
||||
|
||||
## Форма находки
|
||||
|
||||
```
|
||||
### <краткая формулировка ПОСЛЕДСТВИЯ, не симптома>
|
||||
- Файл: internal/<пакет>/<файл>.go:120-134
|
||||
- Severity: critical | major | minor | nit
|
||||
- Confidence: high | medium | low
|
||||
- Оракул: <падающий тест / команда с выводом / положение гайда / нет>
|
||||
- Последствие: <что произойдёт и при каких условиях>
|
||||
- Предложение: <конкретное изменение>
|
||||
- Найдено проходом: <имя агента>
|
||||
```
|
||||
|
||||
## Правила
|
||||
|
||||
- **Заголовок через последствие.** Не «нет проверки токена», а «читатель без
|
||||
токена выгрузит всю историю». Не «слияние перезаписывает запись», а «повторная
|
||||
доставка сотрёт поля у уже сохранённой записи, и восстановить их нечем».
|
||||
Симптом в заголовке — это заявка на то, что читатель сам достроит последствие;
|
||||
он не достроит, он просто починит симптом.
|
||||
- **`critical` без оракула или построенного пути не существует.** Оракул — это
|
||||
падающий тест, вывод выполненной команды или поимённое положение гайда. Не
|
||||
«вероятно, здесь гонка», а прогон детектора гонок с его выводом.
|
||||
- **`confidence: low` — это «так обычно пишут».** Такие находки допустимы, но не
|
||||
поднимаются выше `minor`. Частотность конструкции в публичном коде — не
|
||||
аргумент.
|
||||
- **Находка без поля «Последствие» не выводится вовсе.** Пустое «Последствие:
|
||||
ухудшает читаемость» равносильно отсутствию поля.
|
||||
- **`nit` допустим только при нарушении записанной конвенции** — со ссылкой на
|
||||
файл и раздел конвенций проекта (путь — из раздела `## Карта` брифа) либо на
|
||||
правило линтера. Если правило механизируемо, но не механизировано — это не
|
||||
находка ревью, это `Promote candidate` (см. [promote.md](promote.md)).
|
||||
- **`critical` по основанию «нарушен инвариант проекта» требует брифа.** Ссылка
|
||||
идёт на пункт раздела `## Инварианты` дословно. Без брифа такое основание
|
||||
недоступно — см. [project-brief.md](project-brief.md), деградированный режим.
|
||||
- **Расхождение — не дефект, пока не названо последствие.** Особенно для прохода
|
||||
независимой реализации: «я бы сделал иначе» без последствия не выводится.
|
||||
|
||||
## Шкала severity
|
||||
|
||||
| Severity | Что это | Пример |
|
||||
|---|---|---|
|
||||
| `critical` | нарушение инварианта проекта, потеря или порча данных, утечка секрета, построенный путь к отказу | запись потеряна при слиянии; тело пользовательской выгрузки в поле лога |
|
||||
| `major` | сломанное требование дельта-спеки, необрабатываемый отказ штатного сценария, флаки-тест, поведение вне спеки, меняющее исход | приём отвечает 200, не записав тело: доставка считается принятой, а данных нет |
|
||||
| `minor` | отступление от конвенции с реальной ценой, отсутствующая наблюдаемость, дублирование, которое разойдётся | ни одного чекпоинта на пути разбора: молчащая автоматизация неотличима от пустого потока |
|
||||
| `nit` | нарушение записанной конвенции без последствий за пределами чтения | `msg` с интерполяцией вместо константы |
|
||||
|
||||
Шкала привязана к обратимости, а не к громкости: класс «необратимо и молча»
|
||||
всегда весит больше класса «шумно и лечится повтором». Что здесь необратимо,
|
||||
говорит раздел `## Прод и поток` брифа.
|
||||
|
||||
## Блок границ покрытия
|
||||
|
||||
Каждый проход завершает вывод этим блоком. Он не сокращается и не заменяется
|
||||
фразой «всё проверено».
|
||||
|
||||
```
|
||||
## Coverage of this pass
|
||||
- проверено: <что реально прочитано/запущено, с путями и командами>
|
||||
- не проверялось и почему: <бюджет, недоступный инструмент, вне входа>
|
||||
- принципиально недоступно этому проходу: <из charter'а агента>
|
||||
```
|
||||
|
||||
## Финальный отчёт триажа
|
||||
|
||||
Секции строго в этом порядке, потолок — 7 пунктов в первых двух:
|
||||
|
||||
1. `Блокирует мердж` (≤3, каждая с оракулом);
|
||||
2. `Стоит исправить сейчас` (≤4);
|
||||
3. `Гипотезы без доказательства` — что понижено и почему;
|
||||
4. `Promote candidates` — кандидаты в конвенцию или правило линтера;
|
||||
5. `Границы покрытия` — сводная, обязательная.
|
||||
|
||||
Перед секциями — сводка для человека: профиль и режим прогона, состояние гейта,
|
||||
**перечень запущенных проходов поимённо с исходом каждого**, сколько находок
|
||||
пришло на вход и сколько осталось. Перечень обязателен: пропуск прохода не
|
||||
отличим от прохода без находок, и назвать его больше некому.
|
||||
|
||||
Каждая находка в секциях 1–2 несёт дополнительное поле:
|
||||
|
||||
```
|
||||
- Действие: инлайн | развилка
|
||||
```
|
||||
|
||||
`инлайн` — оркестратор чинит сам, не спрашивая и не логируя. `развилка` — цена
|
||||
исправления сопоставима с переработкой, либо выбор меняет scope, либо решение
|
||||
трогает инвариант: уезжает вопросом с вариантами и ценой каждого туда, где
|
||||
проект держит вопросы, а работа продолжается на остатке.
|
||||
|
||||
Потребитель отчёта — оркестратор, который **реализует прочитанное**. Поэтому
|
||||
потолок в 7 пунктов — не забота о внимании читателя, а защита кодовой базы от
|
||||
правок, которых никто не заказывал.
|
||||
@@ -0,0 +1,208 @@
|
||||
# Бриф проекта — контракт
|
||||
|
||||
Конвейер общий, а находки — проектные. Проход, не знающий, что в этом проекте
|
||||
нельзя нарушать, чем краснеет гейт и сколько данных реально проходит через узел,
|
||||
выдаёт правдоподобные общие места: их дорого опровергать и нечем подтверждать.
|
||||
|
||||
Поэтому проектная специфика живёт **в одном файле проекта**, а не в charter'ах
|
||||
агентов. Charter описывает **метод** прохода (что он делает и почему именно так),
|
||||
бриф — **предмет** (что здесь дорого, чем это меряется, где лежит).
|
||||
|
||||
Шаблон для заполнения — [brief-template.md](brief-template.md).
|
||||
|
||||
## Где лежит и как находится
|
||||
|
||||
Порядок разрешения пути, одинаковый для скилла и для каждого агента:
|
||||
|
||||
1. путь, названный в задании конвейера (`бриф: <путь>`) — конвейер обязан его
|
||||
передавать каждому проходу;
|
||||
2. `docs/review-brief.md`;
|
||||
3. `.claude/review-brief.md`;
|
||||
4. брифа нет — **деградированный режим** (см. ниже).
|
||||
|
||||
Разрешает путь конвейер, один раз, и дальше передаёт готовым. Агент, получивший
|
||||
путь в задании, сам ничего не ищет.
|
||||
|
||||
## Деградированный режим
|
||||
|
||||
Брифа нет — проходы работают, но их recall падает предсказуемым образом, и это
|
||||
**обязано быть названо**, а не сглажено. Каждый проход без брифа:
|
||||
|
||||
- не присваивает `critical` по основанию «нарушен инвариант проекта» — инвариантов
|
||||
он не знает;
|
||||
- не оперирует числами объёма и потока — формулирует условиями;
|
||||
- пишет в границы покрытия строку: «брифа проекта нет: инварианты, модель угроз и
|
||||
профиль нагрузки неизвестны; находки этих классов не искались».
|
||||
|
||||
Триаж сводит эти строки в одну и выносит в финальный отчёт. Отсутствие брифа —
|
||||
дыра покрытия, а не нейтральное умолчание.
|
||||
|
||||
## Форма
|
||||
|
||||
Markdown. Разделы — заголовки второго уровня с **точными именами** из списка
|
||||
ниже: по ним агенты находят свой кусок. Порядок разделов свободен, лишние разделы
|
||||
допустимы и игнорируются, отсутствующий раздел работает как деградированный режим
|
||||
для тех проходов, которые его читают.
|
||||
|
||||
## Разделы
|
||||
|
||||
### `## Проект` — обязателен
|
||||
|
||||
Абзац: что система делает — и, что важнее, **чего она не делает**. Граница домена
|
||||
нужна архитектурному проходу как критерий: «хранилище, а не аналитика», «единое
|
||||
ядро, тонкие транспорты», «связующий сервис, а не медиатека». Без неё перенос
|
||||
понятия через границу выглядит просто новым кодом.
|
||||
|
||||
Читают: `architecture`, `rubric`, `reimpl`, `specs`.
|
||||
|
||||
### `## Инварианты` — обязателен
|
||||
|
||||
Список того, что нарушать нельзя. Каждый пункт — три вещи:
|
||||
|
||||
- формулировка **как проверяемое свойство**, а не как лозунг: «точка сохраняется
|
||||
дословно: незнакомое поле не отбрасывается», а не «бережно относимся к данным»;
|
||||
- **последствие нарушения** и его обратимость;
|
||||
- **severity по умолчанию** — если это не `critical`, скажи прямо.
|
||||
|
||||
Это единственный раздел, который **цитируется формулировкой**, а не пересказывается
|
||||
ссылкой: по нему присваивается severity, и пересказ здесь стоит неверной оценки.
|
||||
|
||||
Читают: `specs` (режим 1 — отражены ли задетые инварианты в спеке), `code`,
|
||||
`adversary`, `architecture`, `triage` (ранжирование и разметка «развилка»).
|
||||
|
||||
### `## Гейт` — обязателен
|
||||
|
||||
- **Команда** целиком, включая передачу базы диффа (`task gate BASE=<база>`), и
|
||||
как база определяется по умолчанию.
|
||||
- **Где логи** отдельных шагов.
|
||||
- **Что означает каждый исход**: чем гейт краснеет, что предупреждает, что
|
||||
пропускается по составу диффа.
|
||||
- **Шаги, которые красят безусловно, и почему.** Это самая ценная часть раздела:
|
||||
«данные под контролем версий», «структура конфига изменилась, а образец нет»,
|
||||
«миграции не накатываются с нуля» — проход обязан знать, что здесь не бывает
|
||||
«ну это мелочь».
|
||||
- **Чего в гейте намеренно нет** и почему — прогон на живом корпусе, длинный
|
||||
интеграционный тест. У проверки, которую гейт не гоняет, краснота никому не
|
||||
видна; это уезжает в границы покрытия.
|
||||
|
||||
Читает: `gate`.
|
||||
|
||||
### `## Команды` — обязателен
|
||||
|
||||
Что проход имеет право выполнить и чем:
|
||||
|
||||
- **карта проекта для архитектуры** — команда, отдающая пакеты, граф зависимостей
|
||||
и инвентарь концепций (`task review:context`);
|
||||
- **запуск изменения вживую** — чем поднять и как проверить поведение (нужно
|
||||
пайплайну задачи на шаге поведенческой верификации);
|
||||
- **тесты, линт, дополнительные проверки** — и какие из них дорогие;
|
||||
- **что запускать запрещено**: рабочая БД, боевой каталог данных, внешние
|
||||
сервисы. Формулируй запретом с путями, а не «будь осторожен».
|
||||
|
||||
Читают: `architecture`, `gate`, `ops`, `triage`, пайплайн задачи.
|
||||
|
||||
### `## Прод и поток` — обязателен
|
||||
|
||||
Материал для эксплуатационного прохода, и он же — половина ранжирования триажа:
|
||||
|
||||
- где это работает: машина, окружение, что рядом, кто перезапускает;
|
||||
- **внешние зависимости поимённо** и чем каждая отказывает: не только «падает», но
|
||||
и «отвечает медленно», «молчит», «отдаёт мусор». Эксплуатационный проход
|
||||
спрашивает про каждую отдельно, и список зависимостей он взять больше неоткуда;
|
||||
- **кто заметит отказ и когда** — есть ли вообще наблюдатель;
|
||||
- **характер потока**: непрерывный и молчаливый, по запросу, по расписанию; есть
|
||||
ли обратная связь у отправителя;
|
||||
- **измеренные числа с провенансом**: объёмы, размеры тел, темп, размеры таблиц.
|
||||
Число без источника проход обязан превратить в условие — так и напиши, откуда
|
||||
оно;
|
||||
- **что обратимо, а что нет.** Падение, которое лечится повтором, и тихая потеря,
|
||||
которую нечем восстановить, — разные классы, и порядок находок в отчёте зависит
|
||||
от того, какой из них здесь главный.
|
||||
|
||||
Читают: `ops`, `adversary`, `triage`, `reimpl`.
|
||||
|
||||
### `## Модель угроз` — обязателен
|
||||
|
||||
- **что недоверенное** и каким каналом приходит: тело запроса, файл, аргумент
|
||||
команды, ответ внешней системы, содержимое архива;
|
||||
- **из чего строятся пути и ключи** — раскладка файлов на диске, состав
|
||||
координатного ключа записи, имя каталога. Враждебный проход выводит запись за
|
||||
пределы песочницы именно отсюда, и без этого пункта он ищет вслепую;
|
||||
- **что разграничивает доступ** — токены, контуры, права файлов;
|
||||
- **что чувствительнее чего**: если данные дороже секретов, скажи это прямо;
|
||||
- **что вне модели** — перечислить явно. Пустой пункт «вне модели» означает, что
|
||||
враждебный проход выдумает угрозу сам, и находка никогда не будет исправлена.
|
||||
|
||||
Читает: `adversary`.
|
||||
|
||||
### `## Карта` — обязателен
|
||||
|
||||
Где что лежит, путями:
|
||||
|
||||
- актуальные спеки и дельта-спеки предлагаемого изменения;
|
||||
- конвенции прозой — и **какая их часть уже механизирована** правилом (её проход
|
||||
по конвенциям не проверяет);
|
||||
- архитектура и решения; журнал проскочивших дефектов;
|
||||
- **единые точки проекта** — где генерируются идентификаторы и время, где
|
||||
единственный парсер входного формата, где маппинг доменной ошибки в код ответа,
|
||||
где общий путь приёма. Это материал для вопроса «не появился ли второй способ»;
|
||||
если команда карты проекта их выгружает, здесь хватит ссылки на неё;
|
||||
- **нумерованные артефакты** — путь миграций и правило нумерации: батч раздаёт
|
||||
номера заранее, чтобы параллельные задачи не столкнулись файлами;
|
||||
- где ведутся задачи (пайплайн только читает и сообщает исход);
|
||||
- `testdata` и что в них лежит; куда можно писать временное;
|
||||
- **каталоги, которые не трогают вовсе**.
|
||||
|
||||
Читают: все проходы.
|
||||
|
||||
### `## Типовые узлы` — необязателен, но без него рубрика беднеет
|
||||
|
||||
Роды узлов, из которых состоит проект (парсер входного формата, HTTP-обработчик,
|
||||
репозиторий, воркер, клиент внешнего API, CLI-команда, файловое хранилище), и по
|
||||
3–5 **специфичных для рода** проверяемых свойств к каждому.
|
||||
|
||||
Читает: `rubric`. Без раздела рубрика выродится в общие слова и повторит
|
||||
конвенции — то есть станет applicative-проходом, ради отсутствия которого она и
|
||||
существует.
|
||||
|
||||
### `## Триггеры` — необязателен
|
||||
|
||||
Проектная конкретизация правила выбора профиля: какие пути и контракты означают
|
||||
`deep`; что считается «поведением, видимым снаружи»; при каком изменении
|
||||
запускается `reimpl`. Умолчания записаны в самом скилле и работают без этого
|
||||
раздела — но общее правило говорит «изменение публичного контракта», а какой
|
||||
контракт публичный, знает только проект.
|
||||
|
||||
Читают: скилл конвейера, пайплайн задачи.
|
||||
|
||||
### `## Недоступно проверке` — обязателен
|
||||
|
||||
Что не проверит ни один проход и почему: поведение внешних систем и их будущих
|
||||
версий, реальный профиль нагрузки, соответствие сохранённого действительности,
|
||||
завязка внешних потребителей на текущую форму, суждение «а нужна ли эта
|
||||
функциональность».
|
||||
|
||||
Этот раздел целиком уезжает в границы покрытия финального отчёта. Он существует
|
||||
ровно затем, чтобы «критичных проблем не обнаружено» никогда не читалось как
|
||||
«проверено всё».
|
||||
|
||||
Читает: `triage`; каждый проход — свою часть.
|
||||
|
||||
## Правила ведения
|
||||
|
||||
- **Бриф не пересказывает документацию проекта.** Факт, записанный в `CLAUDE.md`
|
||||
или в архитектуре, попадает сюда ссылкой и одной строкой сути. Два дома для
|
||||
одного факта разъезжаются, и разошедшийся бриф хуже отсутствующего: он выглядит
|
||||
актуальным. Исключение одно — раздел инвариантов, он цитируется.
|
||||
- **Числа — с провенансом.** «Тела доходили до 42 МБ (замер, ссылка)». Число без
|
||||
источника проход не имеет права использовать как утверждение.
|
||||
- **Что вне модели — называется явно.** Это относится и к угрозам, и к нагрузке,
|
||||
и к классам находок, которые проект сознательно перестал проверять.
|
||||
- **Бриф подчиняется промоуту.** Свойство, ставшее правилом линтера, из брифа
|
||||
вычёркивается — как и из конвенций, и из charter'ов (см.
|
||||
[promote.md](promote.md), шаг 3).
|
||||
- **Когда обновлять:** сменился гейт; появился новый контур, зависимость или
|
||||
источник входа; журнал ревью получил запись вида «проход не мог этого знать».
|
||||
Планового пересмотра нет.
|
||||
- **Бриф ведёт проект**, а не плагин. Плагин его только читает и никогда не
|
||||
правит.
|
||||
@@ -0,0 +1,93 @@
|
||||
# Промоут: находка → конвенция → правило → удаление
|
||||
|
||||
Механизм храповика. Без него конвейер выдаёт одни и те же находки бесконечно, а
|
||||
конвенции не растут — то есть внимание тратится повторно на уже решённое.
|
||||
|
||||
Роли уровней:
|
||||
|
||||
- **generative-проходы** — механизм *открытия* неявного (дорого, шумно, но
|
||||
только они достают то, чего нет в списках);
|
||||
- **конвенции** — дешёвая *регрессионная сетка* на уже открытое;
|
||||
- **правила линтера** — то же с детерминированным оракулом и нулевой ценой
|
||||
внимания.
|
||||
|
||||
## Шаг 1. Находка → конвенция
|
||||
|
||||
Условия: находка **принята** при ревью (не отвергнута, не понижена в гипотезу) и
|
||||
**не специфична для одного места**.
|
||||
|
||||
- Формулируется как **проверяемое свойство**, а не как совет: «уровень доменного
|
||||
отказа выбирает единственный логирующий чекпоинт», а не «внимательнее с
|
||||
уровнями логов».
|
||||
- Записывается источник — какой проход нашёл. Это единственные данные для
|
||||
калибровки: проход, чьи находки регулярно доезжают до конвенции, оправдан;
|
||||
проход, чьи находки не доезжают никогда, — кандидат на `drop` (см.
|
||||
[calibration.md](calibration.md)).
|
||||
- Место записи — файл конвенций проекта (путь — в разделе `## Карта` брифа). Если
|
||||
тема относится к поведению системы, а не к тому, как мы пишем код, — это не
|
||||
конвенция, а требование: заводится дельта-спека обычным путём.
|
||||
|
||||
Промоут идёт **тем же путём, что change → spec**: правка попадает в тот же
|
||||
коммит, что и исправление кода, с пометкой в сообщении — история промоутов
|
||||
остаётся видна в `git log` по файлу конвенций.
|
||||
|
||||
## Шаг 2. Конвенция → правило
|
||||
|
||||
Как только свойство выражается детерминированно, оно переезжает в инструмент.
|
||||
Порядок предпочтения — от дешёвого к дорогому:
|
||||
|
||||
1. **готовое правило существующего линтера** — включить в конфиг;
|
||||
2. **запрет идентификатора или импорта** правилом-«запретителем» с собственным
|
||||
паттерном;
|
||||
3. **правило с настройкой формы** — когда важно не имя, а конструкция;
|
||||
4. **тест-сканер исходников** — когда правило про структуру проекта или про
|
||||
схему: направление зависимостей, форма миграций, матчинг ошибки по тексту,
|
||||
бизнес-логика в транспорте;
|
||||
5. **собственный анализатор** — последний рубеж, заводим только если 1–4 не
|
||||
выражают правило.
|
||||
|
||||
Правило обязано быть **зелёным на текущем коде в момент включения**: иначе
|
||||
хук блокирует любой коммит, и правило снимут первым же раздражённым движением.
|
||||
Приводить код в соответствие — часть шага 2, отдельным коммитом.
|
||||
|
||||
## Шаг 3. Удаление из конвенций, из брифа и из промптов
|
||||
|
||||
**Шаг, который пропускают чаще всего, и единственный, ради которого затевались
|
||||
первые два.**
|
||||
|
||||
Как только правило работает:
|
||||
|
||||
- из файла конвенций убирается формулировка правила; остаётся, если нужно, одна
|
||||
строка «проверяется линтером `<имя>`» — но только там, где без неё раздел
|
||||
теряет связность;
|
||||
- **из брифа проекта** убирается соответствующий пункт, а в разделе `## Карта`
|
||||
правило переезжает в перечень «механизировано и потому проходом по конвенциям
|
||||
не проверяется»;
|
||||
- из контекста инструмента спек убирается дубль, если он там был.
|
||||
|
||||
Charter'ы проходов при этом **не правятся**: они общие и живут в плагине, а
|
||||
предмет проверки приходит из брифа. Именно поэтому шаг 3 стал дешевле, чем был:
|
||||
вычеркнуть строку в одном файле проекта, а не в девяти промптах.
|
||||
|
||||
Практический критерий: **в прозаических конвенциях остаётся только то, что
|
||||
принципиально не выражается правилом.** Файл конвенций на несколько сотен строк
|
||||
размазывает внимание модели по тривиальному — она добросовестно проверит
|
||||
именование полей лога и не дойдёт до формы решения. Каждая строка конвенций,
|
||||
которую можно было бы проверить машиной, оплачивается непойманным дефектом
|
||||
где-то ещё.
|
||||
|
||||
## Обратное движение
|
||||
|
||||
Правило, которое даёт ложные срабатывания чаще, чем ловит (порядка трети от
|
||||
общего числа), снимается и возвращается в прозу — или удаляется совсем, если
|
||||
свойство перестало быть важным. Снятие фиксируется там же, где включалось, с
|
||||
одной строкой «почему».
|
||||
|
||||
## Что промоуту не подлежит
|
||||
|
||||
- Находка, специфичная для одного места (её лечит комментарий в коде).
|
||||
- Вкусовщина: не меняет поведения, не влияет на стоимость следующего изменения,
|
||||
не нарушает записанного. Такое выбрасывается на триаже и не хранится.
|
||||
- Свойство, требующее знания рантайма (профиль нагрузки, история инцидентов) —
|
||||
его нельзя проверить ни промптом, ни линтером; место такому — в журнале ревью
|
||||
как «признано неавтоматизируемым» (см. [review-journal.md](review-journal.md)).
|
||||
@@ -0,0 +1,62 @@
|
||||
# Журнал проскочивших дефектов
|
||||
|
||||
Артефакт проекта, а не плагина: файл живёт в репозитории (путь — в разделе
|
||||
`## Карта` брифа, по умолчанию `docs/review-journal.md`). Здесь описано, зачем он
|
||||
и какой формы, потому что без него конвейер не учится: находки закрываются,
|
||||
причины непоймания теряются, и один и тот же класс проскакивает второй раз.
|
||||
|
||||
## Что туда попадает
|
||||
|
||||
Дефект, который **прошёл ревью и всплыл позже**. Записывается **сразу**, а не
|
||||
ретроспективно: со временем теряется не сам факт, а причина непоймания —
|
||||
единственное, ради чего журнал существует.
|
||||
|
||||
Реализованные задачи, находки ревью и принятые решения сюда не пишутся: у них
|
||||
есть коммит, спека и задача. Здесь только промахи конвейера.
|
||||
|
||||
Отдельно сюда попадают **решения о составе прогонов**: перестали звать проход,
|
||||
понизили профиль правилом, сузили класс проверяемого. Не потому, что это промах,
|
||||
а потому, что здесь лежит цена: если что-то теперь проскочит, первый вопрос —
|
||||
«не тот ли это класс, который мы перестали проверять».
|
||||
|
||||
## Форма записи
|
||||
|
||||
```
|
||||
## ГГГГ-ММ-ДД — <краткое последствие>
|
||||
|
||||
- **Где:** путь:строка либо «конвейер, а не код»
|
||||
- **Симптом:** как обнаружилось, кем и когда
|
||||
- **Причина:** что на самом деле было не так
|
||||
- **Почему не поймали:** какой проход обязан был найти и что ему помешало
|
||||
- **Что меняем:** правило прохода, шаг гейта, конвенция, пункт брифа — либо
|
||||
«ничего, цена поимки выше цены дефекта»
|
||||
```
|
||||
|
||||
Последний пункт важнее остальных. Вывод «ничего не меняем» — законный исход: не
|
||||
всякий дефект стоит того, чтобы усложнять ради него ревью каждой задачи.
|
||||
|
||||
## Куда ведёт запись
|
||||
|
||||
Три адреса, и выбор между ними — половина ценности журнала:
|
||||
|
||||
- **в бриф проекта** — если проход не мог знать факта: объём, характер потока,
|
||||
что здесь необратимо, какой шаг гейта красит безусловно. Самый частый адрес и
|
||||
самый дешёвый.
|
||||
- **в конвенции или в правило линтера** — если свойство выражается
|
||||
детерминированно (процедура — [promote.md](promote.md)).
|
||||
- **в charter прохода** — если сломан **метод**, а не знание. Правка charter'а
|
||||
меняет поведение во всех проектах, поэтому она требует калибровки
|
||||
([calibration.md](calibration.md)) и обоснования, почему это не лечится
|
||||
брифом.
|
||||
|
||||
## Что журнал даёт конвейеру
|
||||
|
||||
- **пробы для калибровки** — реальный проскочивший дефект сильнее синтетического:
|
||||
синтетические смещены в сторону тех, которые уже умеешь придумывать;
|
||||
- **основание для правил конвейера** — требование называть запущенные проходы
|
||||
поимённо, отказ от чисел, производных от размера корпуса, и правило
|
||||
последовательного прогона выведены из конкретных записей, а не из общих
|
||||
соображений;
|
||||
- **счётчик обратимости решений** — сузили состав проходов и через месяц поймали
|
||||
дефект ровно того класса, который перестали проверять: решение пересматривается
|
||||
фактом, а не спором.
|
||||
@@ -0,0 +1,229 @@
|
||||
---
|
||||
name: task-batch
|
||||
description: Проводит несколько задач разом — планирует порядок и пересечения, гонит каждую задачу отдельным сабагентом в своём git worktree через task-pipeline, интегрирует по одной ветке через rebase + fast-forward (линейная история), проверяет полноту ревью каждой ветки и в конце сверяет стыки, возникшие от слияния. Набор задач приходит извне. Использовать, когда просят сделать несколько задач сразу.
|
||||
---
|
||||
|
||||
# Батч задач
|
||||
|
||||
Оркестратор **набора** задач. Планирует порядок, раскидывает задачи по
|
||||
изолированным worktree, каждую проводит через полный цикл `task-pipeline`, затем
|
||||
сводит в основную ветку линейной историей и делает финальную сверку. Тонкая
|
||||
обёртка над `task-pipeline` — не переизобретай её шаги, вызывай как есть.
|
||||
|
||||
Работай **максимально автономно**, по тому же принципу, что и одиночный пайплайн:
|
||||
вопрос, который решать не тебе, записывается и не останавливает поток; спрашиваем
|
||||
только про **необратимое** (деплой, выкладка наружу, удаление или перезапись
|
||||
рабочих данных). Механику — планирование, worktree, rebase, интеграцию, чистку —
|
||||
делаем без спроса.
|
||||
|
||||
Перед стартом прочитай `CLAUDE.md` проекта и бриф ревью (`docs/review-brief.md`):
|
||||
из него берутся команда гейта, инварианты и раскладка нумерованных артефактов.
|
||||
|
||||
## Границы
|
||||
|
||||
- **Набор задач приходит извне.** Батч его не формирует: не выбирает из беклога,
|
||||
не приоритизирует, не решает, что важнее. Набор не задан — попроси его у
|
||||
вызывающего и остановись.
|
||||
- **Батч не владеет спринтом и целями.** Он сообщает исход по каждой задаче в тех
|
||||
же трёх словах, что и `task-pipeline`: сделана / не доведена / оказалась крупнее
|
||||
задачи.
|
||||
|
||||
## Ключевое отличие от одиночного пайплайна
|
||||
|
||||
`task-pipeline` коммитит **в текущую ветку**, и при ручном запуске это основная
|
||||
ветка. Здесь так нельзя для параллельных задач, поэтому батч — **осознанное
|
||||
исключение**: временные ветки и worktree заводятся лишь как средство изоляции, а
|
||||
конечное состояние — та же линейная история основной ветки через rebase +
|
||||
fast-forward. Ветки после вливания удаляются.
|
||||
|
||||
## Модель исполнения
|
||||
|
||||
- Каждая задача = **один автономный сабагент** (`general-purpose`, чтобы иметь
|
||||
доступ к Skill и Agent для вложенных чекпоинтов ревью), работающий **только в
|
||||
своём worktree** и прогоняющий `task-pipeline` целиком на этой задаче.
|
||||
- Оркестратор кода задач не пишет: он планирует, заводит worktree, запускает
|
||||
сабагентов, проверяет полноту их ревью, интегрирует ветки и делает финальную
|
||||
сверку.
|
||||
- Стиль правок внутри — заточка под проект и конвенции, right-size, без
|
||||
золочения.
|
||||
|
||||
## Шаги
|
||||
|
||||
### 1. Прочитать набор
|
||||
|
||||
Набор задан списком (слаги, файлы, описания) — прочитай файл каждой задачи и
|
||||
связанные спеки и черновики. Задачи-идеи включаются, но помни: сабагент проведёт
|
||||
их сперва через `opsx:explore`, это тяжелее и чаще упирается в вопрос.
|
||||
|
||||
### 2. Спланировать порядок и пересечения (автономно)
|
||||
|
||||
Для каждой задачи определи:
|
||||
|
||||
- **затронутые capability** — по её описанию и по каталогу актуальных спек
|
||||
(`openspec/specs/`);
|
||||
- **жёсткие зависимости**: задача B строится на результате A → A строго раньше B;
|
||||
- **нумерованные артефакты — номера раздаёт оркестратор заранее.** Если проект
|
||||
нумерует миграции или подобные файлы (путь — из брифа), посмотри последний
|
||||
номер и **раздай номера тем задачам, которые, вероятно, их добавят**, до
|
||||
запуска. Номер уходит в charter сабагента, и он берёт назначенный, а не
|
||||
«следующий свободный». Так такие задачи можно гнать одновременно: файлы не
|
||||
столкнутся, а описание схемы правят разные строки — конфликт мелкий и решается
|
||||
на интеграции;
|
||||
- **жёстко сериализуем** (не гоняем одновременно) настоящие пересечения:
|
||||
- **одна capability на несколько задач** — две задачи, правящие одну спеку (тем
|
||||
более одно и то же `### Requirement`), дают не текстовый, а **семантический**
|
||||
конфликт при архивации; сериализуем по смыслу, а не только по файлам;
|
||||
- пересечение по одним и тем же исходникам;
|
||||
- **мягкие конфликты** сериализовать не надо: индекс беклога (каждая задача
|
||||
убирает свою строку) и спеки разных capability — разные строки и файлы,
|
||||
сливаются сами.
|
||||
|
||||
Собери план: **волны** параллельно-безопасных задач плюс сериализованный хвост
|
||||
конфликтоопасных, с учётом зависимостей. Покажи план короткой репликой и иди
|
||||
дальше.
|
||||
|
||||
### 3. Свежая база
|
||||
|
||||
Убедись, что рабочее дерево чистое и основная ветка свежая. Зафиксируй базовый
|
||||
коммит. Новые ветки бери от свежей вершины; ветки следующей волны — от вершины,
|
||||
уже включающей результат предыдущих волн.
|
||||
|
||||
### 4. Прогнать волны
|
||||
|
||||
**Потолок параллелизма — 2–3 задачи одновременно.** Каждая задача тянет полный
|
||||
`task-pipeline` с вложенным ревью и гейтом, поэтому больше трёх разом душат
|
||||
машину и провоцируют гонки. Волну шире трёх бей на под-пачки по ≤3.
|
||||
|
||||
**Волна из одной задачи — не вырожденный случай, а обязательный.** Задача,
|
||||
ревью которой будет доказывать находки **числами** (профиль `deep`, где работают
|
||||
`adversary` и `ops`: удержание блокировки, пик памяти, рост файлов, длительность
|
||||
операции), гонится в волне одна. Соседний прогон на той же машине портит эти
|
||||
числа, а находка с испорченным оракулом хуже отсутствующей — она выглядит
|
||||
доказанной. Если задача всё же пошла в общей волне, её отчёт обязан нести строку
|
||||
в границах покрытия: замеры сняты под соседней нагрузкой.
|
||||
|
||||
Для каждой задачи в под-пачке:
|
||||
|
||||
1. Заведи worktree и ветку от текущей вершины:
|
||||
`git worktree add <path> -b task/<slug> <основная ветка>`. Путь — во временном
|
||||
каталоге проекта (`./tmp`), не в системном `/tmp`.
|
||||
2. Запусти **по одному сабагенту на задачу, все в одном сообщении**,
|
||||
`subagent_type: general-purpose`. Charter сабагента:
|
||||
- работай **строго в своём worktree** `<path>`; в другие каталоги и в основную
|
||||
ветку не лезь;
|
||||
- прогони Skill **`task-pipeline`** ровно на этой задаче, полный цикл SDD с
|
||||
обоими чекпоинтами ревью;
|
||||
- если задаче назначен **номер артефакта** — используй строго его;
|
||||
- **профиль ревью выбирается по факту изменения.** Батч не повод понижать
|
||||
профиль: «нас много и мы спешим» — это ровно тот стимул, из-за которого
|
||||
проходы пропускают;
|
||||
- **режим прогона проходов — последовательный.** Твой worktree не один на
|
||||
машине;
|
||||
- **вернуть отчёт**, в котором обязательно: исход задачи одним из трёх слов;
|
||||
что сделано; какие вопросы записаны и куда; изменённые файлы; добавлялся ли
|
||||
нумерованный артефакт и с каким номером; затронутые capability; состояние
|
||||
гейта; **перечень запущенных проходов ревью поимённо с исходом каждого** и
|
||||
границы покрытия.
|
||||
|
||||
Сабагент, упершийся в вопрос, **не останавливает батч**: он записывает вопрос,
|
||||
режет задачу до остатка и доводит остаток — либо, если остатка нет, возвращает
|
||||
исход «не доведена». Оркестратор собирает такие вопросы и выносит их в финальный
|
||||
доклад пачкой.
|
||||
|
||||
### 5. Проверить полноту ревью — до интеграции
|
||||
|
||||
**Ветка, чей отчёт не называет проходы поимённо, не вливается.** Пропуск прохода
|
||||
не отличим от прохода без находок, и на уровне батча это ещё опаснее: отчётов
|
||||
много, каждый выглядит полным, а сверять их некому, кроме тебя.
|
||||
|
||||
По каждой готовой ветке сверь перечень проходов с таблицей профилей скилла
|
||||
`review-pipeline` для объявленного профиля. Расхождение — не повод отменять
|
||||
задачу: дозапусти недостающие проходы **на ветке**, в её worktree, через
|
||||
`review-pipeline`, и только потом интегрируй. Отчёт дозапуска приложи к отчёту
|
||||
задачи.
|
||||
|
||||
### 6. Интегрировать — rebase + fast-forward, по одной ветке
|
||||
|
||||
Сводим ветки **строго последовательно** (линейная история), в порядке
|
||||
зависимостей. Вливаем **только зелёные**.
|
||||
|
||||
Для каждой готовой ветки `task/<slug>`:
|
||||
|
||||
- `git rebase <основная> task/<slug>` — перенос на текущую вершину;
|
||||
- резолв конфликтов (их почти нет — конфликтоопасное сериализовано, номера
|
||||
розданы заранее). Неавтоматический конфликт — **не форсируй**: прерви
|
||||
(`git rebase --abort`), оставь ветку и worktree как есть, вынеси это в доклад
|
||||
как нераспознанное пересечение;
|
||||
- `git checkout <основная> && git merge --ff-only task/<slug>`;
|
||||
- после каждой интеграции — **гейт на основной ветке**. Красное — **откати эту
|
||||
интеграцию** (`git reset --hard` на прошлую вершину), ветку с worktree сохрани,
|
||||
задачу перечисли в докладе. Основная ветка **никогда** не остаётся
|
||||
полузелёной;
|
||||
- только после зелёного: `git worktree remove <path>` и
|
||||
`git branch -d task/<slug>`.
|
||||
|
||||
**Политика частичного провала.** Упавшая задача (исход «не доведена», красные
|
||||
тесты в её worktree, конфликт при rebase) **не блокирует остальные**: интегрируем
|
||||
все зелёные, упавшую оставляем в её worktree и ветке нетронутой — ничего не
|
||||
удаляем, — и перечисляем в докладе с причиной, отчётом и путём к worktree.
|
||||
|
||||
### 7. Финальный гейт
|
||||
|
||||
На основной ветке после всех интеграций — гейт целиком. Зелёное обязательно; пока
|
||||
красное, шаг 8 не начинается.
|
||||
|
||||
### 8. Финальная сверка — только то, чего не видел никто
|
||||
|
||||
Каждая задача уже прошла полный конвейер в своём worktree. Повторять его на
|
||||
интегрированном диффе бессмысленно: те же проходы на тех же файлах дадут те же
|
||||
находки и удорожат триаж. Здесь проверяется **только то, что появилось от
|
||||
слияния**:
|
||||
|
||||
- запусти **по одному `review-specs` на каждую затронутую capability**. Набор
|
||||
назван поимённо и замеров эти проходы не делают, поэтому их допустимо гнать
|
||||
одним сообщением — это то самое отступление от последовательного режима,
|
||||
которое правило разрешает. Задание сузь до стыков: не сверять capability
|
||||
целиком заново, а искать **рассинхрон код↔спека, возникший от слияния** —
|
||||
требование, которое одна задача выполнила, а соседняя незаметно отменила; два
|
||||
change, по-разному описавшие одно поведение;
|
||||
- если задачи пересекались по файлам, добавь один `review-architecture` на
|
||||
интегрированный дифф с вопросом «не появился ли второй способ делать то, что
|
||||
уже делается» — именно он возникает, когда две задачи независимо решали
|
||||
похожее.
|
||||
|
||||
Замечания отрабатывай как в `task-pipeline`: `инлайн` чини сам, `развилка` —
|
||||
вопросом в запись; после правок — снова гейт.
|
||||
|
||||
### 9. Прибраться и доложить
|
||||
|
||||
- Убери worktree и ветки **только успешно влитых** задач, в конце
|
||||
`git worktree prune`. Worktree и ветки **провалившихся** не трогай — они нужны
|
||||
для ручного дожатия.
|
||||
- Доложи кратко:
|
||||
- **исход по каждой задаче** одним из трёх слов, с хешем коммита;
|
||||
- план волн и порядок интеграции;
|
||||
- вопросы, записанные сабагентами, пачкой;
|
||||
- что дозапускалось на шаге 5 и почему;
|
||||
- итог финальной сверки и ссылки на архивные change;
|
||||
- **отдельно — провалившиеся** задачи с причиной и путём к оставленному
|
||||
worktree;
|
||||
- **границы покрытия сводной строкой**, включая задачи, чьи замеры снимались в
|
||||
общей волне.
|
||||
|
||||
## Тонкости
|
||||
|
||||
- **Изоляция параллельных тестов.** Прежде чем гнать несколько прогонов разом,
|
||||
убедись, что тесты не делят фиксированный порт или файл БД (обычно берут
|
||||
временный каталог и эфемерный порт — тогда ок). Делят — гони такие задачи
|
||||
последовательно.
|
||||
- Поведенческая верификация внутри сабагента поднимает изменение вживую: следи,
|
||||
чтобы соседние worktree не дрались за порты и рабочие каталоги. Если проект
|
||||
умеет поднимать только один экземпляр — такие задачи в одну волну не ставь.
|
||||
- Ревью выполненного — **до** закрытия задачи; это забота `task-pipeline` внутри
|
||||
каждого сабагента, дублировать не надо.
|
||||
- `openspec validate --strict` тоже внутри `task-pipeline` — не пропускай его
|
||||
своими правками на интеграции.
|
||||
- Крупная переработка, предложенная ревью внутри задачи, — развилка: не вливай
|
||||
молча, вынеси в доклад.
|
||||
- Держи вызывающего в цикле короткими репликами на переходах фаз (план → волны →
|
||||
интеграция → финальная сверка), но не проси подтверждать механику.
|
||||
@@ -0,0 +1,274 @@
|
||||
---
|
||||
name: task-pipeline
|
||||
description: Автономно проводит одну задачу через полный цикл Spec Driven Development — от постановки до коммита (opsx explore→propose→ревью спек профилем design→apply→ревью кода→archive→коммит), с обязательными чекпоинтами ревью и докладом об исходе. Использовать, когда просят взять/сделать задачу или довести идею до реализации.
|
||||
---
|
||||
|
||||
# Пайплайн задачи
|
||||
|
||||
Оркестратор **одной** задачи по Spec Driven Development: проводит её от
|
||||
постановки до коммита максимально автономно. Механику не согласовываем — делаем.
|
||||
|
||||
Это тонкая обёртка над каноническими скиллами `opsx:explore` / `opsx:propose` /
|
||||
`opsx:apply` / `opsx:archive` — вызывай их через Skill, не переизобретай их шаги.
|
||||
Ревью — скилл `review-pipeline`, он же держит правило выбора профиля.
|
||||
|
||||
Перед стартом прочитай `CLAUDE.md` проекта и то, на что он ссылается
|
||||
(архитектура, конвенции), если ещё не в контексте. Проектные факты, нужные ревью
|
||||
— инварианты, команда гейта, объёмы, модель угроз, — живут в брифе
|
||||
(`docs/review-brief.md`, контракт — в references конвейера ревью).
|
||||
|
||||
## Границы: чем пайплайн не владеет
|
||||
|
||||
- **Беклогом, спринтом, целями и приоритетами.** Задача приходит извне. Пайплайн
|
||||
её не выбирает, не приоритизирует, не заводит и не переоценивает; если в
|
||||
проекте есть свой процесс управления задачами — он и решает, что брать.
|
||||
- **Определением ценности.** «Нужна ли эта функциональность» — не вопрос
|
||||
пайплайна ни на одном шаге.
|
||||
|
||||
Пайплайн владеет **своим** определением готовности (ниже) и **сообщает
|
||||
наблюдаемый исход**. Что с исходом делать дальше — не его дело.
|
||||
|
||||
## Наблюдаемые исходы
|
||||
|
||||
Ровно три, и каждый обязан быть назван в докладе прямо:
|
||||
|
||||
- **сделана** — определение готовности выполнено целиком;
|
||||
- **не доведена** — с причиной и с записанным вопросом; что именно сделано и до
|
||||
какой границы, названо явно;
|
||||
- **оказалась крупнее задачи** — распознаётся **до заведения change**, иначе его
|
||||
придётся выбрасывать. Дальше — декомпозиция, и это не работа пайплайна.
|
||||
|
||||
## Определение готовности
|
||||
|
||||
Задача сделана, когда верно всё:
|
||||
|
||||
1. гейт проекта зелёный;
|
||||
2. ревью проведено **по профилю**, состав прогона сверен с таблицей профилей
|
||||
поимённо, непущенные проходы названы в границах покрытия;
|
||||
3. change заархивирован, дельты влиты в актуальные спеки;
|
||||
4. коммит сделан в текущую ветку;
|
||||
5. **критерии приёмки, если проект их дал**, проверены поимённо, у каждого назван
|
||||
оракул и исход. Критерии приходят снаружи; пайплайн их не сочиняет и не
|
||||
занижает. Расхождение «критерии закрыты, а суть задачи не достигнута» — дефект
|
||||
критериев, и о нём сообщается, а не молча дорабатывается.
|
||||
|
||||
Пункты 1–4 — своё. Пункт 5 — внешнее, и проверяется только если оно дано.
|
||||
|
||||
## Принцип автономности
|
||||
|
||||
**Умолчание — делать, а не спрашивать.** Задача доводится до коммита без участия
|
||||
человека; предполагается, что так пройдёт большинство задач.
|
||||
|
||||
Наткнулся на вопрос, который решать не тебе, — **не останавливайся и не
|
||||
спрашивай**. Запиши его и продолжай:
|
||||
|
||||
1. **Запиши вопрос там, где проект держит вопросы** (секция беклога, файл
|
||||
задачи, трекер — это знает проект). Если проект не сказал, куда, — отдельной
|
||||
секцией `Вопросы` в своём докладе, и это тоже исход. Тело отвечает на три
|
||||
вещи: **что именно решить**, **какие есть варианты и цена каждого**, **что
|
||||
стоит, пока решения нет**. Плюс твоя рекомендация — человек чаще соглашается,
|
||||
чем выбирает заново, и готовое суждение экономит ему весь контекст.
|
||||
2. **Переформулируй задачу на остаток** — то, что делается без этого решения.
|
||||
Назови границу: докуда доводим сейчас.
|
||||
3. Доведи остаток до конца и закоммить. Задача не «висит на вопросе», она сделана
|
||||
в объявленных границах.
|
||||
|
||||
**Что остатком не является** — две оговорки, без которых правило вредит:
|
||||
|
||||
- **остаток, который записывает в хранилище или в журнал состояние, зависящее от
|
||||
нерешённого, — не остаток.** Решение поднимается до начала записи. Иначе
|
||||
нерешённое материализуется в данные, а данные переживают решение;
|
||||
- **остаток, из которого пропала польза, названная в постановке, — не остаток.**
|
||||
Это исход «не доведена», а не «сделана в границах».
|
||||
|
||||
Нет полезного остатка — задача заканчивается исходом «не доведена», вопрос
|
||||
записан, ничего не коммитится наполовину.
|
||||
|
||||
### Когда всё-таки спрашивать
|
||||
|
||||
Узко и по другому основанию — не «сложное решение», а **необратимое действие**:
|
||||
|
||||
- деплой, выкладка наружу, смена публичного адреса или токенов;
|
||||
- удаление или перезапись рабочих данных, включая подрезку архивов;
|
||||
- всё, что уходит за пределы машины.
|
||||
|
||||
Здесь ошибка не откатывается коммитом, поэтому спрашиваем даже когда решение
|
||||
кажется очевидным. Развилка в дизайне — вопрос в запись; необратимое действие —
|
||||
вопрос человеку сейчас.
|
||||
|
||||
Стиль правок — заточка под проект и конвенции, right-size, без золочения.
|
||||
|
||||
## Шаги
|
||||
|
||||
### 1. Прочитать задачу
|
||||
|
||||
Задача задана извне (slug, файл, ссылка, описание) — прочитай её и связанные
|
||||
спеки и черновики. Не задана — попроси у вызывающего; сам в беклог не лезь и
|
||||
приоритеты не интерпретируй.
|
||||
|
||||
Если проект даёт задаче **критерии приёмки**, выпиши их сразу: на шаге 3 они
|
||||
уезжают в `tasks.md` change. Файл задачи может быть удалён до коммита, а
|
||||
критерии обязаны его пережить.
|
||||
|
||||
Оцени тривиальность (влияет на шаг 4):
|
||||
|
||||
- **тривиальная** — локальная правка без изменения поведения, спек и схемы,
|
||||
решение очевидно. Explore и ревью спек пропускаются;
|
||||
- **нетривиальная** — новое или изменённое поведение, дизайн-развилки, задеты
|
||||
инварианты, схема или несколько capability. Полный цикл.
|
||||
|
||||
Здесь же — проверка на «крупнее задачи»: если видно, что одним заходом это не
|
||||
мерджится, объявляй исход **до** заведения change.
|
||||
|
||||
### 2. (Опц.) Груммить идею — `opsx:explore`
|
||||
|
||||
Только для идей и мутных постановок. Вызови Skill `opsx:explore`. Развилку
|
||||
грумминга не выноси на человека — запиши вопросом и груми остаток. Выход: ясная
|
||||
постановка, готовая к propose. **В explore не пишем код.**
|
||||
|
||||
### 3. Завести change — `opsx:propose`
|
||||
|
||||
Вызови Skill `opsx:propose`. Получаем `proposal.md`, дизайн (для нетривиальных),
|
||||
дельта-спеки (`ADDED`/`MODIFIED`/`REMOVED Requirements`), `tasks.md`. Каждое
|
||||
`### Requirement` содержит `SHALL`/`MUST`; структурные заголовки английские,
|
||||
сценарии `GIVEN/WHEN/THEN`. Прогони `openspec validate --strict <id>`.
|
||||
|
||||
Критерии приёмки задачи, если они были, копируются в `tasks.md` отдельным блоком.
|
||||
|
||||
### 4. (Нетривиальная) Ревью предложения — профиль `design`, ДО кода
|
||||
|
||||
Первый чекпоинт. Вызови Skill **`review-pipeline`** с профилем `design`, ссылкой
|
||||
на change `<id>` и путём к брифу. Он запустит `review-specs` (режим «дизайн ДО
|
||||
кода»), `review-rubric` (фаза 1: приёмочные критерии для задуманного узла) и
|
||||
`review-architecture` по предложению.
|
||||
|
||||
Смысл профиля: архитектурная находка на готовом коде стоит переписывания и
|
||||
потому игнорируется — та же находка здесь стоит абзаца обсуждения. Рубрику из
|
||||
`review-rubric` перенеси в `tasks.md` как приёмочные критерии; там же уже лежат
|
||||
критерии от постановки, если они были.
|
||||
|
||||
### 5. Отработать замечания ревью предложения
|
||||
|
||||
- Мелочь и явные улучшения — правь сам в спеках и дизайне.
|
||||
- Развилки (компромисс, scope, инвариант) — вопросом в запись, спеки урезаются на
|
||||
остаток.
|
||||
- После правок перепрогони `openspec validate --strict <id>`.
|
||||
|
||||
### 6. Написать код — `opsx:apply`
|
||||
|
||||
Вызови Skill `opsx:apply` для реализации `tasks.md`. Код — по конвенциям проекта
|
||||
(файл назван в разделе `## Карта` брифа). Меняешь схему — обнови её описание в
|
||||
документации тем же change, если проект этого требует: гейт обычно это проверяет.
|
||||
|
||||
Прогони гейт и добейся зелёного — он же гейт следующего шага.
|
||||
|
||||
**Поведенческая верификация.** Если задача меняет реальное поведение (новый
|
||||
эндпоинт, разбор входа, схема, форма ответа) — зелёных юнит-тестов мало. Подними
|
||||
изменение вживую командой из раздела `## Команды` брифа и прогони сценарий.
|
||||
Пропусти только для чисто внутренних правок без наблюдаемого рантайма.
|
||||
|
||||
**Сервис не оставляем лежать.** Если запуск упал — почини или откати до конца
|
||||
шага.
|
||||
|
||||
### 7. Ревью кода — Skill `review-pipeline`
|
||||
|
||||
Второй чекпоинт. Вызови Skill **`review-pipeline`**, дав ссылку на change `<id>`,
|
||||
базу диффа, путь к брифу, профиль **и режим запуска**. Профиль выбирается по
|
||||
факту изменения, а не по ощущению важности; общее правило — в скилле, проектные
|
||||
триггеры — в брифе:
|
||||
|
||||
- миграция схемы, новый пакет, публичный контракт, правило идентичности или
|
||||
слияния данных → `deep`;
|
||||
- иначе меняется поведение, видимое снаружи → `standard`;
|
||||
- иначе (багфикс, локальная правка, доки) → `quick`.
|
||||
|
||||
**Режим по умолчанию последовательный, и обосновывать его не надо.** Параллельно
|
||||
гоняем только тогда, когда об этом попросили явно **и назвали набор** — какие
|
||||
именно проходы или какую стадию. Просьба без набора основанием не считается:
|
||||
гони последовательно и скажи строкой, что набор не был назван. Причина умолчания
|
||||
— замеры: `adversary` и `ops` доказывают находки числами, а два меряющих прохода
|
||||
на одной машине портят числа друг другу; находка с испорченным оракулом хуже
|
||||
отсутствующей, потому что выглядит доказанной.
|
||||
|
||||
Скилл сам гоняет гейт, нужные проходы и обязательный триаж. Возвращает отчёт с
|
||||
потолком 7 пунктов, разметкой `Действие: инлайн | развилка` и секцией границ
|
||||
покрытия.
|
||||
|
||||
**Сверь состав прогона с таблицей профилей в скилле, прежде чем коммитить.**
|
||||
Пропуск прохода не отличим от прохода без находок: гейт зелёный, спеки сошлись,
|
||||
отчёт выглядит полным. Единственный, кто мог бы заметить пропуск, — триаж, а он
|
||||
заполняется тем, что ему подали. Отчёт обязан называть запущенные проходы
|
||||
**поимённо и с исходом**; непущенный идёт строкой «не запускался» в границы
|
||||
покрытия. Реестр короткий (4–8 проходов) — сверка стоит одного взгляда, а
|
||||
молчащий пропуск уже стоил семи находок и отдельной задачи на их дозакрытие.
|
||||
|
||||
Отработай так же, как шаг 5: помеченное `инлайн` чини сам и не логируй,
|
||||
`развилка` — вопросом в запись (он уже сформулирован триажем, его остаётся
|
||||
перенести). После правок — снова гейт.
|
||||
|
||||
**Границы покрытия из отчёта не выбрасывай** — они уезжают в финальный доклад
|
||||
сжатой строкой. Отчёт, из которого исчезло «что проверить было невозможно»,
|
||||
превращается в ложное ощущение проверенности.
|
||||
|
||||
Отчёт триажа сохрани вместе с change (`openspec/changes/<id>/review/`): по нему
|
||||
потом видно, что было найдено и что из этого осталось незаведённым.
|
||||
|
||||
### 8. Архивировать — `opsx:archive`
|
||||
|
||||
Вызови Skill `opsx:archive`: change уезжает в архив, дельты вливаются в
|
||||
актуальные спеки.
|
||||
|
||||
### 9. Синк документации и закрытие задачи
|
||||
|
||||
Ревью выполненного — **до** закрытия. Затем:
|
||||
|
||||
- суть переехавшего решения — в документацию проекта (архитектура, журнал
|
||||
решений), если её там ещё нет;
|
||||
- менялась схема — её описание обновлено тем же change;
|
||||
- новое, узнанное о внешнем формате или о данных, — в тот файл проекта, который
|
||||
это накапливает; такой файл обычно ценнее кода;
|
||||
- **задача закрывается процедурой проекта** — своей у пайплайна нет. Есть скилл
|
||||
или скрипт беклога — вызови его; нет — скажи в докладе, что задача сделана и
|
||||
закрытие остаётся за вызывающим. Не выдумывай формат чужого индекса.
|
||||
|
||||
### 10. Коммит
|
||||
|
||||
Коммить **в текущую ветку** (`git rev-parse --abbrev-ref HEAD`), сам ветку не
|
||||
создавай и не переключай, ничего не пушь. При ручном запуске HEAD обычно на
|
||||
основной ветке — коммит идёт прямо в неё; под оркестратором `task-batch` HEAD на
|
||||
ветке задачи в изолированном worktree, и делать дополнительно ничего не нужно.
|
||||
|
||||
Сообщение — по-русски, скиллом `commit`, если он подключён (первая строка «что
|
||||
сделано», тело списком 1–3 пункта, без трейлеров). Одна задача — один осмысленный
|
||||
коммит.
|
||||
|
||||
Готово — доложи кратко:
|
||||
|
||||
- **исход** задачи одним из трёх слов и, если не «сделана», чем ограничен
|
||||
результат;
|
||||
- что сделано, какие вопросы записаны и куда;
|
||||
- ссылка на архивный change;
|
||||
- исход по каждому критерию приёмки, если они были;
|
||||
- **одна строка границ покрытия**: какой профиль и режим гонялись, какие проходы
|
||||
не запускались и что проверить было невозможно. Доклад без неё сообщает
|
||||
«проверено», не сообщая, что именно.
|
||||
|
||||
## Тонкости
|
||||
|
||||
- **Не завязывайся на основную ветку и корень репозитория.** Скилл работает в
|
||||
текущем worktree и на текущей ветке: не делай `git checkout`/`switch`, не
|
||||
создавай веток, не пушь.
|
||||
- Не пропускай `openspec validate --strict` перед архивацией.
|
||||
- Тривиальная задача: шаги 2 и 4 пропускаются; ревью кода (шаг 7) остаётся
|
||||
всегда, но в профиле `quick`.
|
||||
- Гейт блокирует: пока он красный, опиниативные проходы не запускаются. Чинить и
|
||||
перезапускать, а не «посмотреть заодно».
|
||||
- Если ревью предлагает крупную переработку — это развилка: не правь молча и не
|
||||
спрашивай, запиши вопросом и доведи остаток.
|
||||
- Держи вызывающего в цикле короткими репликами на переходах фаз, но не проси
|
||||
подтверждать механику.
|
||||
- **Занизить профиль ревью или пропустить проход — самый дешёвый способ
|
||||
«ускориться», и он же самый дорогой по последствиям.** Защита одна: профиль
|
||||
выбирается по факту изменения, состав сверяется поимённо, а непущенное
|
||||
называется в отчёте. Пропуск, названный строкой, стоит строки; пропуск молчащий
|
||||
стоил семи находок.
|
||||
Reference in New Issue
Block a user