--- name: task-batch description: Проводит несколько задач разом — планирует порядок и пересечения, гонит каждую задачу отдельным сабагентом в своём git worktree через task-pipeline, интегрирует по одной ветке через rebase + fast-forward (линейная история), проверяет полноту ревью каждой ветки и в конце сверяет стыки, возникшие от слияния. Набор задач приходит извне. Использовать, когда просят сделать несколько задач сразу. --- # Батч задач Оркестратор **набора** задач. Планирует порядок, раскидывает задачи по изолированным worktree, каждую проводит через полный цикл `av-dev-pipeline:task-pipeline`, затем сводит в основную ветку линейной историей и делает финальную сверку. Тонкая обёртка над пайплайном задачи — не переизобретай её шаги, вызывай как есть. Работай **максимально автономно**, по тому же принципу, что и одиночный пайплайн: вопрос, который решать не тебе, записывается и не останавливает поток; спрашиваем только про **необратимое** (деплой, выкладка наружу, удаление или перезапись рабочих данных). Механику — планирование, worktree, rebase, интеграцию, чистку — делаем без спроса. ## Предпосылки - **OpenSpec и скиллы `opsx:*`** — на них стоит цикл внутри каждого сабагента и проход `review-specs` финальной сверки. Проекта без OpenSpec это касается так же, как одиночного пайплайна (см. его раздел «Предпосылки»). - **Скиллы зовутся с пространством имён**: `av-dev-pipeline:task-pipeline`, `av-dev-pipeline:review-pipeline`, `av-dev-pipeline:project-brief`. Короткое имя может разрешиться в устаревшую проектную копию, и это произойдёт молча — в charter'е сабагента пиши полное имя, он твоего контекста не видит. - **Проектные копии этих скиллов и агентов при установке плагина удаляются.** Перед стартом прочитай `CLAUDE.md` проекта и бриф ревью (`docs/review-brief.md`): из него берутся **основная ветка** (раздел `## Карта` — она подставляется в каждую команду git ниже), команда гейта, инварианты и раскладка нумерованных артефактов. Брифа нет — заведи его Skill'ом **`av-dev-pipeline:project-brief`** один раз, до первой волны: иначе каждая задача батча заплатит деградированным ревью, а имя основной ветки придётся угадывать. ## Границы - **Набор задач приходит извне.** Батч его не формирует: не выбирает из беклога, не приоритизирует, не решает, что важнее. Набор не задан — попроси его у вызывающего и остановись. - **Батч не владеет спринтом и целями.** Он сообщает исход по каждой задаче в тех же трёх словах, что и `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; - **замеряющая задача** — та, чьё ревью будет доказывать находки **числами**, и потому она гонится в волне **одна** (обоснование — ниже, в шаге 4). Решается здесь, на планировании, а не во время прогона: состав волны определяется сейчас, а профиль ревью сабагент выберет только внутри задачи, и ключевать волну на ещё не сделанный выбор нельзя. Триггеры — по фактам о задаче, каждый сам по себе достаточен: - трогает схему хранилища, миграцию, формат на диске или объём хранимого; - трогает конкурентность: транзакции, блокировки, фоновые циклы, общее состояние; - трогает размер тела, буфер, память, сжатие, ретеншен, темп потока; - её тема названа в разделах `## Прод и поток` или `## Прецеденты` брифа как место, где уже мерили или уже ломалось. Ни один триггер не сработал — задача не замеряющая, даже если её ревью окажется `deep`. `deep` про глубину проверки, замеряющая — про соревнование за железо; это разные вопросы, и совпадают они не всегда; - **нумерованные артефакты — номера раздаёт оркестратор заранее.** Если проект нумерует миграции или подобные файлы (путь — из брифа), посмотри последний номер и **раздай номера всем задачам, которые, вероятно, их добавят**, до запуска. Номер уходит в charter сабагента, и он берёт назначенный, а не «следующий свободный». **Это отдельная механика от правила волны, и она ему не служит** — их раньше путали, и они тянули в разные стороны. Правило волны отвечает на вопрос «кто с кем гонится одновременно», предраздача — на вопрос «какой номер берёт задача». Раздача нужна там, где **две задачи одной под-пачки** добавляют нумерованный артефакт: каждая считает «следующий свободный» по основной ветке, которая ещё не видела соседку, и обе берут один номер. Миграции под это почти не попадают — миграция и так триггер замеряющей задачи, а замеряющая идёт одна; но нумерованные артефакты бывают не только миграциями. Поэтому номера раздаются **всем** задачам с таким артефактом, независимо от того, в какой волне они окажутся: раздача ничего не стоит, а её отсутствие ловится только конфликтом на интеграции. Между волнами проблемы нет — ветка следующей волны берётся от вершины, уже включающей предыдущие; - **жёстко сериализуем** (не гоняем одновременно) настоящие пересечения: - **одна capability на несколько задач** — две задачи, правящие одну спеку (тем более одно и то же `### Requirement`), дают не текстовый, а **семантический** конфликт при архивации; сериализуем по смыслу, а не только по файлам; - пересечение по одним и тем же исходникам; - **мягкие конфликты** сериализовать не надо: файлы-перечни, где каждая задача правит **свою** строку (индексы, оглавления, списки записей), и спеки разных capability — разные строки и файлы, сливаются сами. Собери план: **волны** параллельно-безопасных задач плюс сериализованный хвост конфликтоопасных, с учётом зависимостей; замеряющие задачи стоят в плане отдельными волнами по одной. Покажи план короткой репликой — назвав, какие задачи признаны замеряющими и по какому триггеру, — и иди дальше. ### 3. Свежая база Убедись, что рабочее дерево чистое и основная ветка свежая. Зафиксируй базовый коммит. Новые ветки бери от свежей вершины; ветки следующей волны — от вершины, уже включающей результат предыдущих волн. ### 4. Прогнать волны **Потолок параллелизма — 2–3 задачи одновременно.** Каждая задача тянет полный цикл пайплайна с вложенным ревью и гейтом, поэтому больше трёх разом душат машину и провоцируют гонки. Волну шире трёх бей на под-пачки по ≤3 и **гони под-пачки последовательно**: следующая стартует, когда предыдущая вернула отчёты. Иначе потолок обходится тривиально — шесть задач, запущенных «двумя под-пачками» в одном сообщении, это шесть задач разом. **Волна из одной задачи — не вырожденный случай, а обязательный.** Задача, признанная на шаге 2 **замеряющей**, гонится в волне одна: соседний прогон на той же машине портит числа, а находка с испорченным оракулом хуже отсутствующей — она выглядит доказанной. Если замеряющая задача всё же пошла в общей волне, её отчёт обязан нести строку в границах покрытия: замеры сняты под соседней нагрузкой. Для каждой задачи в под-пачке: 1. Заведи worktree и ветку от текущей вершины: `git worktree add -b task/ <основная ветка>`. Путь — во временном каталоге проекта (`./tmp`), не в системном `/tmp`. 2. Запусти **по одному сабагенту на задачу, все в одном сообщении**, `subagent_type: general-purpose`. Charter сабагента: - работай **строго в своём worktree** ``; в другие каталоги и в основную ветку не лезь; - прогони Skill **`av-dev-pipeline:task-pipeline`** ровно на этой задаче, полный цикл SDD с обоими чекпоинтами ревью; - если задаче назначен **номер артефакта** — используй строго его; - **профиль ревью выбирается по факту изменения.** Батч не повод понижать профиль: «нас много и мы спешим» — это ровно тот стимул, из-за которого проходы пропускают; - **режим прогона проходов — последовательный.** Твой worktree не один на машине; - **если вложенные сабагенты недоступны** (движок не даёт запускать агентов из агента) — не пропускай ревью и не понижай профиль: проведи его **инлайн** по тем же charter'ам `av-dev-pipeline`, сохранив обязательное — гейт до опиниативных проходов, состав по профилю, триаж последним. И **скажи в отчёте прямым текстом, что ревью шло инлайн**: инлайновый проход видит контекст автора и потому декоррелирован слабее — это меняет доверие к результату, а не только способ запуска; - **вернуть отчёт**, в котором обязательно: исход задачи одним из трёх слов; **объявленный профиль ревью и режим прогона**; что сделано; какие вопросы записаны и куда; изменённые файлы; добавлялся ли нумерованный артефакт и с каким номером; затронутые capability; состояние гейта; **перечень запущенных проходов ревью поимённо с исходом каждого**; **путь к сохранённому отчёту триажа** (`openspec/changes//review/`); шло ли ревью инлайн; границы покрытия. Сабагент, упершийся в вопрос, **не останавливает батч**: он записывает вопрос, режет задачу до остатка и доводит остаток — либо, если остатка нет, возвращает исход «не доведена». Оркестратор собирает такие вопросы и выносит их в финальный доклад пачкой. ### 5. Проверить полноту ревью — до интеграции **Ветка, чей отчёт не называет профиль и проходы поимённо, не вливается.** Пропуск прохода не отличим от прохода без находок, и на уровне батча это ещё опаснее: отчётов много, каждый выглядит полным, а сверять их некому, кроме тебя. Сверка идёт в три шага, и порядок важен: 1. **Возьми объявленный профиль** из отчёта задачи — он затем и заказан в обязательных полях шага 4. Профиля в отчёте нет — перечень проходов сверять не с чем; это само по себе основание не вливать, пока сабагент не назовёт профиль и не обоснует его по факту изменения. 2. **Сверяй с независимым артефактом, а не с прозой отчёта.** Перечень проходов бери из **сохранённого отчёта триажа** (`openspec/changes//review/`) — пайплайн обязан его туда положить. Проза сабагента написана тем же, кто мог проход и пропустить: она подтверждает сама себя. Отчёта триажа на месте нет — считай, что состав неизвестен, и дозапускай ревью целиком. 3. **Сверь состав** с таблицей профилей скилла `av-dev-pipeline:review-pipeline` для объявленного профиля. Расхождение — не повод отменять задачу: дозапусти недостающие проходы **на ветке**, в её worktree, через `av-dev-pipeline:review-pipeline`, и только потом интегрируй. **Находки дозапуска — такие же находки, и зелёный гейт их не отменяет.** Правило интеграции «вливаем только зелёные» смотрит на гейт, а дозапущенный `critical` гейт не красит: он был бы пропущен молча, если это не сказать прямо. Поэтому: - `critical` или `major` из дозапуска — **вливание этой ветки останавливается**. Помеченное `инлайн` чинится в её worktree, после починки — гейт, затем интеграция. Помеченное `развилка` — вопрос в запись, задача режется до остатка ровно так же, как это сделал бы пайплайн внутри; - остатка нет — ветка не вливается и уходит в доклад как провалившаяся, со своим worktree; - `minor` и `nit` из дозапуска — в урожай доклада, вливанию не мешают. Отчёт дозапуска приложи к отчёту задачи и назови в докладе (шаг 9), почему он понадобился: систематический пропуск одного и того же прохода — находка о самом конвейере, а не о задаче. ### 6. Интегрировать — rebase + fast-forward, по одной ветке Сводим ветки **строго последовательно** (линейная история), в порядке зависимостей. Вливаем **только зелёные**. **Ветка задачи занята её worktree, и это определяет форму команд.** Пока worktree жив (а удаляется он последним, после зелёного гейта), ветка `task/` checkout'нута в нём, и `git rebase <основная> task/` из главного worktree **падает**: `fatal: 'task/' is already used by worktree at …`. Поэтому rebase делается **внутри worktree задачи**, а ff-слияние — из главного. Для каждой готовой ветки `task/`: - `git -C rebase <основная>` — перенос ветки задачи на текущую вершину, выполняется в её собственном worktree; - резолв конфликтов (их почти нет — конфликтоопасное сериализовано, номера розданы заранее). Неавтоматический конфликт — **не форсируй**: прерви (`git -C rebase --abort`), оставь ветку и worktree как есть, вынеси это в доклад как нераспознанное пересечение; - **ненулевой код `rebase` относится к этой ветке и только к ней.** Прерванный rebase в чужом worktree не трогает ни главное дерево, ни остальные ветки: проверь `git -C status` и `git status` — обе чистые. Уводить весь батч в провалившиеся из-за одного ненулевого кода запрещено: это ложная причина, из-за которой зелёные задачи не доедут до основной ветки. Провалилась одна — провалилась одна; - из главного worktree (он стоит на основной ветке — проверь `git rev-parse --abbrev-ref HEAD`): `git merge --ff-only task/`. Ветку в главном дереве **не переключай** — `git checkout task/` тоже упрётся в занятость; - после каждой интеграции — **гейт на основной ветке**. Красное — **откати эту интеграцию** (`git reset --hard` на прошлую вершину), ветку с worktree сохрани, задачу перечисли в докладе. Основная ветка **никогда** не остаётся полузелёной; - только после зелёного: `git worktree remove ` и `git branch -d task/` — в этом порядке, иначе ветка снова занята. **Политика частичного провала.** Упавшая задача (исход «не доведена», красные тесты в её worktree, конфликт при rebase, невлитая из-за находок дозапуска) **не блокирует остальные**: интегрируем все зелёные, упавшую оставляем в её worktree и ветке нетронутой — ничего не удаляем, — и перечисляем в докладе с причиной, отчётом и путём к worktree. Причина называется **настоящая**: «конфликт rebase в файле X», а не «нераспознанное пересечение» на всякий случай. ### 7. Финальный гейт На основной ветке после всех интеграций — гейт целиком. Зелёное обязательно; пока красное, шаг 8 не начинается. ### 8. Финальная сверка — только то, чего не видел никто Каждая задача уже прошла полный конвейер в своём worktree. Повторять его на интегрированном диффе бессмысленно: те же проходы на тех же файлах дадут те же находки и удорожат триаж. Здесь проверяется **только то, что появилось от слияния**: - запусти **по одному `review-specs` на каждую затронутую capability**. Набор назван поимённо и замеров эти проходы не делают, поэтому их допустимо гнать одним сообщением — это то самое отступление от последовательного режима, которое правило разрешает. Задание сузь до стыков: не сверять capability целиком заново, а искать **рассинхрон код↔спека, возникший от слияния** — требование, которое одна задача выполнила, а соседняя незаметно отменила; два change, по-разному описавшие одно поведение; - если задачи пересекались по файлам, добавь один `review-architecture` на интегрированный дифф с вопросом «не появился ли второй способ делать то, что уже делается» — именно он возникает, когда две задачи независимо решали похожее. Замечания отрабатывай как одиночный пайплайн: `инлайн` чини сам, `развилка` — вопросом в запись; после правок — снова гейт. ### 9. Прибраться и доложить - Убери worktree и ветки **только успешно влитых** задач, в конце `git worktree prune`. Worktree и ветки **провалившихся** не трогай — они нужны для ручного дожатия. - **Задачи батч не закрывает** — ни одну, ни свои, ни чужие записи учёта не трогает. Он сообщает исход по каждой; закрытие происходит после приёмки и делается владельцем спринта. - Доложи кратко: - **исход по каждой задаче** одним из трёх слов, с хешем коммита; - план волн и порядок интеграции, с пометкой, какие задачи шли по одной как замеряющие; - вопросы, записанные сабагентами, пачкой; - что дозапускалось на шаге 5 и почему; шло ли где-то ревью инлайн; - итог финальной сверки и ссылки на архивные change; - **`Урожай`** — отложенные находки всех задач одним списком, с провенансом. Задачи из него заводит тот, кто ведёт задачи проекта, а не батч; - **отдельно — провалившиеся** задачи с настоящей причиной и путём к оставленному worktree; - **границы покрытия сводной строкой**, включая задачи, чьи замеры снимались в общей волне, и ветки, где ревью шло инлайн. ## Тонкости - **Изоляция параллельных тестов.** Прежде чем гнать несколько прогонов разом, убедись, что тесты не делят фиксированный порт или файл БД (обычно берут временный каталог и эфемерный порт — тогда ок). Делят — гони такие задачи последовательно. - Поведенческая верификация внутри сабагента поднимает изменение вживую: следи, чтобы соседние worktree не дрались за порты и рабочие каталоги. Если проект умеет поднимать только один экземпляр — такие задачи в одну волну не ставь. - Ревью выполненного — **до** интеграции; это забота `av-dev-pipeline:task-pipeline` внутри каждого сабагента, дублировать не надо. - `openspec validate --strict` тоже внутри пайплайна задачи — не пропускай его своими правками на интеграции. - Крупная переработка, предложенная ревью внутри задачи, — развилка: не вливай молча, вынеси в доклад. - Держи вызывающего в цикле короткими репликами на переходах фаз (план → волны → интеграция → финальная сверка), но не проси подтверждать механику.