--- name: task-batch description: Проводит несколько задач разом — планирует порядок и пересечения, гонит каждую задачу отдельным сабагентом в своём git worktree через task-pipeline (по умолчанию по одной задаче за раз; параллельно по графу зависимостей — по явной просьбе), интегрирует по одной ветке через rebase + fast-forward (линейная история), проверяет полноту ревью каждой ветки и в конце сверяет стыки, возникшие от слияния. Набор задач приходит извне. Использовать, когда просят сделать несколько задач сразу. --- # Батч задач Оркестратор **набора** задач. Планирует порядок, раскидывает задачи по изолированным worktree, каждую проводит через полный цикл `av-dev-pipeline:task-pipeline`, затем сводит в основную ветку линейной историей и делает финальную сверку. Тонкая обёртка над пайплайном задачи — не переизобретай её шаги, вызывай как есть. **По умолчанию задачи идут по одной**, в порядке зависимостей. Параллельно — по явной просьбе, и тогда параллельность **по графу зависимостей**: одновременно гонится только то, между чем нет ни зависимости, ни пересечения. Правило и его цена — в шаге 4. Работай **максимально автономно**, по тому же принципу, что и одиночный пайплайн: вопрос, который решать не тебе, записывается и не останавливает поток; спрашиваем только про **необратимое** (деплой, выкладка наружу, удаление или перезапись рабочих данных). Механику — планирование, worktree, rebase, интеграцию, чистку — делаем без спроса. ## Предпосылки - **OpenSpec и скиллы `opsx:*`** — на них стоит цикл внутри каждого сабагента и проход `review-specs` финальной сверки. Проекта без OpenSpec это касается так же, как одиночного пайплайна (см. его раздел «Предпосылки»). - **Скиллы зовутся с пространством имён**: `av-dev-pipeline:task-pipeline`, `av-dev-pipeline:review-pipeline`, `av-dev-pm:tasks`. Короткое имя может разрешиться в устаревшую проектную копию, и это произойдёт молча — в charter'е сабагента пиши полное имя, он твоего контекста не видит. - **Проектные копии этих скиллов и агентов при установке плагина удаляются.** Перед стартом прочитай `CLAUDE.md` проекта: оттуда берутся **имя основной ветки** (оно подставляется в каждую команду git ниже), команда и семантика гейта, инварианты и что запускать запрещено. Раскладка нумерованных артефактов — `docs/database.md` и `docs/.pm.json` (ключ `migrations`). **Документов канона нет — проект к нему не приведён.** Скажи это строкой и предложи `av-dev-pm:canon` **до первой задачи**: иначе каждая задача батча заплатит поразрядной деградацией ревью, а имя основной ветки придётся угадывать. ## Границы - **Набор задач приходит извне.** Батч его не формирует: не выбирает из беклога, не приоритизирует, не решает, что важнее. Набор не задан — попроси его у вызывающего и остановись. - **Батч не владеет спринтом и целями.** Он сообщает исход по каждой задаче в тех же трёх словах, что и `task-pipeline`: сделана / не доведена / оказалась крупнее задачи. - **Задачи закрывает пайплайн внутри каждого сабагента**, шагом 11 — после коммита работы и **отдельным коммитом учёта**, вызовом Skill `av-dev-pm:tasks`. Батч сам записей учёта не трогает: он не знает, чем кончилась приёмка, и дублировать закрытие ему незачем. Но грязное дерево после сабагента — **его** проблема: на нём откажут и `rebase`, и `worktree remove` (см. шаг 6). Урожай ревью батч отдаёт списком, а задачи из него заводит тот, кто ведёт задачи проекта. ## Ключевое отличие от одиночного пайплайна `task-pipeline` коммитит **в текущую ветку**, и при ручном запуске это основная ветка. Здесь так нельзя, поэтому батч — **осознанное исключение**: временные ветки и worktree заводятся лишь как средство изоляции, а конечное состояние — та же линейная история основной ветки через rebase + fast-forward. Ветки после вливания удаляются. Изоляция нужна **в обоих режимах, а не только в параллельном**: батч не вливает ветку, пока не проверил полноту её ревью (шаг 5), и упавшая задача обязана остаться в своём worktree для ручного дожатия (шаг 6), не оставив следа в основной ветке. В параллельном режиме к этому добавляется вторая причина — задачи не должны видеть недоделанную работу друг друга. ## Модель исполнения - Каждая задача = **один автономный сабагент** (`general-purpose`, чтобы иметь доступ к Skill и Agent для вложенных чекпоинтов ревью), работающий **только в своём worktree** и прогоняющий `task-pipeline` целиком на этой задаче. - Оркестратор кода задач не пишет: он планирует, заводит worktree, запускает сабагентов, проверяет полноту их ревью, интегрирует ветки и делает финальную сверку. - Стиль правок внутри — заточка под проект и конвенции, right-size, без золочения. ## Шаги ### 1. Прочитать набор Набор задан списком (слаги, файлы, описания) — прочитай файл каждой задачи и связанные спеки и черновики. Сырьё (в терминах `av-dev-pm` — запись типа `research` с пустым разделом «Вопрос») включается, но помни: сабагент проведёт его сперва через `opsx:explore`, это тяжелее и чаще упирается в вопрос. ### 2. Спланировать порядок и пересечения (автономно) Для каждой задачи определи: - **затронутые capability** — по её описанию и по каталогу актуальных спек (`openspec/specs/`); - **жёсткие зависимости**: задача B строится на результате A → A строго раньше B; - **замеряющая задача** — та, чьё ревью будет доказывать находки **числами**. В последовательном прогоне это ничего не меняет: она и так идёт одна. В параллельном она гонится в волне **одна** (обоснование — ниже, в шаге 4). Помечается здесь, на планировании, а не во время прогона, и **независимо от режима**: состав волны определяется сейчас, режим может смениться просьбой уже после плана, а профиль ревью сабагент выберет только внутри задачи — ключевать волну на ещё не сделанный выбор нельзя. Триггеры — по фактам о задаче, каждый сам по себе достаточен: - трогает схему хранилища, миграцию, формат на диске или объём хранимого; - трогает конкурентность: транзакции, блокировки, фоновые циклы, общее состояние; - трогает размер тела, буфер, память, сжатие, ретеншен, темп потока; - её тема названа в `docs/research/` или в журнале `docs/review.md` как место, где уже мерили или уже ломалось. Ни один триггер не сработал — задача не замеряющая, даже если её ревью окажется `deep`. Профиль про глубину проверки, замеряющая — про соревнование за железо; это разные вопросы, и совпадают они не всегда; - **нумерованные артефакты — номера раздаёт оркестратор заранее.** Если проект нумерует миграции (путь — `docs/.pm.json`, ключ `migrations`), посмотри последний номер и **раздай номера всем задачам, которые, вероятно, их добавят**, до запуска. Номер уходит в charter сабагента, и он берёт назначенный, а не «следующий свободный». **Это отдельная механика от правила волны, и она ему не служит** — их раньше путали, и они тянули в разные стороны. Правило волны отвечает на вопрос «кто с кем гонится одновременно», предраздача — на вопрос «какой номер берёт задача». Раздача нужна там, где **две задачи одной под-пачки** добавляют нумерованный артефакт: каждая считает «следующий свободный» по основной ветке, которая ещё не видела соседку, и обе берут один номер. Миграции под это почти не попадают — миграция и так триггер замеряющей задачи, а замеряющая идёт одна; но нумерованные артефакты бывают не только миграциями. Поэтому номера раздаются **всем** задачам с таким артефактом, независимо от того, в какой волне они окажутся: раздача ничего не стоит, а её отсутствие ловится только конфликтом на интеграции. Между волнами проблемы нет — ветка следующей волны берётся от вершины, уже включающей предыдущие; в последовательном прогоне проблемы нет по той же причине, но номера всё равно раздай: режим может смениться просьбой, а раздача бесплатна; - **жёстко сериализуем** (не гоняем одновременно) настоящие пересечения: - **одна capability на несколько задач** — две задачи, правящие одну спеку (тем более одно и то же `### Requirement`), дают не текстовый, а **семантический** конфликт при архивации; сериализуем по смыслу, а не только по файлам; - пересечение по одним и тем же исходникам; - **мягкие конфликты** сериализовать не надо: файлы-перечни, где каждая задача правит **свою** строку (индексы, оглавления, списки записей), и спеки разных capability — разные строки и файлы, сливаются сами. Собери план как **граф зависимостей**, а не как плоский список: рёбра — жёсткие зависимости и сериализуемые пересечения. Дальше по режиму: - **последовательно (умолчание)** — линеаризуй граф в один порядок: топологический, а там, где он оставляет свободу, — раньше то, от чего зависит больше задач, и раньше то, что правит общие для набора места. Волн нет, замеряющие задачи ничем не отличаются от прочих; - **параллельно (по просьбе)** — нарежь граф на **волны**: в одну волну попадают только задачи, между которыми нет ребра; замеряющие стоят отдельными волнами по одной. Рёбра у графа двух видов, и путать их не надо: **зависимость** направлена (B без результата A не делается), **пересечение** — нет (кто первый, неважно, лишь бы не разом). Тот же словарь у графа проходов ревью — см. «Порядок прогона» в `av-dev-pipeline:review-pipeline`. ```mermaid flowchart TD A["A: схема хранилища
(замеряющая)"] B["B: эндпоинт поверх A"] C["C: формат лога"] D["D: правит ту же capability, что C"] A -->|зависимость| B C -. пересечение — одна capability .- D ``` Этот граф даёт: **последовательно** — `A → B → C → D` (или `A → C → B → D`, обе линеаризации законны); **параллельно** — волна 1 `A` одна (замеряющая), волна 2 `B` и `C`, волна 3 `D`. Схемы в этом скилле — **пример и сводка**, правила ставит текст: при расхождении прав он. (В `av-dev-pipeline:review-pipeline` наоборот — там граф прогона и есть алгоритм, и старший он.) Покажи план короткой репликой — режим, порядок или состав волн, какие задачи признаны замеряющими и по какому триггеру, — и иди дальше. ### 3. Свежая база Убедись, что рабочее дерево чистое и основная ветка свежая. Зафиксируй базовый коммит. Новые ветки бери от свежей вершины; ветку следующей задачи (в параллельном режиме — ветки следующей волны) — от вершины, уже включающей результат предыдущих. ### 4. Провести задачи **Умолчание — по одной задаче за раз, в порядке из шага 2.** Следующая стартует, когда предыдущая вернула отчёт и (если она зелёная) влилась. Обосновывать это не надо — обосновывается отступление. Причина умолчания в том, что задача батча дороже прохода ревью: каждая тянет полный цикл пайплайна с гейтом, поднятием сервиса и вложенным ревью, и две такие на одной машине дерутся за порты, рабочие каталоги, СУБД и само железо. Последовательный прогон к тому же оставляет ревью внутри задачи его собственное умолчание — **параллельные проходы**: машина свободна, и выигрыш берётся там, где он ничего не стоит. **Параллельно — по явной просьбе, и параллельность идёт по графу зависимостей.** Одновременно гонится только то, между чем на шаге 2 не нашлось ребра: ни жёсткой зависимости, ни общей capability, ни общих исходников. «Гони параллельно» не означает «гони всё разом» — граф остаётся в силе, просьба лишь разрешает использовать его ширину. В параллельном режиме действуют два ограничения: - **потолок — 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 с обоими чекпоинтами ревью; - если задаче назначен **номер артефакта** — используй строго его; - **профиль ревью выбирается по факту изменения.** Батч не повод понижать профиль: «нас много и мы спешим» — это ровно тот стимул, из-за которого проходы пропускают; - **режим прогона проходов ревью — от режима батча**, и его называет charter, а не сабагент: батч идёт по одной задаче → режим умолчательный, **`по графу`** (машина свободна); батч идёт волнами → **`линейно`**, твой worktree не один на машине, и этой причиной ты обязан объяснить режим в отчёте. Внутренние рёбра графа — цепочку проходов, держащих машину, и барьер стоимости — конвейер соблюдает сам, в любом режиме; - **если вложенные сабагенты недоступны** (движок не даёт запускать агентов из агента) — не пропускай ревью и не понижай профиль: проведи его **инлайн** по тем же charter'ам `av-dev-pipeline`, сохранив обязательное — гейт до опиниативных проходов, состав по профилю, триаж последним. И **скажи в отчёте прямым текстом, что ревью шло инлайн**: инлайновый проход видит контекст автора и потому разведён с ним слабее — это меняет доверие к результату, а не только способ запуска; - **вернуть отчёт**, в котором обязательно: исход задачи одним из трёх слов; **объявленный профиль ревью и режим прогона**; что сделано; какие вопросы записаны и куда; изменённые файлы; добавлялся ли нумерованный артефакт и с каким номером; затронутые capability; состояние гейта; **перечень запущенных проходов ревью поимённо с исходом каждого**; **путь к сохранённому отчёту триажа** (`openspec/changes//review/`, после архивации — `openspec/changes/archive//review/`); шло ли ревью инлайн; границы покрытия. **Шаги 5 и 6 отрабатываются на вернувшейся задаче до старта следующей** — в параллельном режиме на вернувшейся волне до старта следующей. Иначе ветка следующей возьмётся от вершины, не видевшей предыдущую работу, и весь смысл порядка из шага 2 теряется. Сабагент, упершийся в вопрос, **не останавливает батч**: он записывает вопрос, режет задачу до остатка и доводит остаток — либо, если остатка нет, возвращает исход «не доведена». Оркестратор собирает такие вопросы и выносит их в финальный доклад пачкой. ### 5. Проверить полноту ревью — до интеграции **Ветка, чей отчёт не называет профиль и проходы поимённо, не вливается.** Пропуск прохода не отличим от прохода без находок, и на уровне батча это ещё опаснее: отчётов много, каждый выглядит полным, а сверять их некому, кроме тебя. Сверка идёт в три шага, и порядок важен: 1. **Возьми объявленный профиль** из отчёта задачи — он затем и заказан в обязательных полях шага 4. Профиля в отчёте нет — перечень проходов сверять не с чем; это само по себе основание не вливать, пока сабагент не назовёт профиль и не обоснует его по факту изменения. 2. **Сверяй с независимым артефактом, а не с прозой отчёта.** Перечень проходов бери из **сохранённого отчёта триажа** (`openspec/changes//review/` или `openspec/changes/archive//review/` — задача доведена, change заархивирован) — пайплайн обязан его туда положить. Проза сабагента написана тем же, кто мог проход и пропустить: она подтверждает сама себя. Отчёта триажа на месте нет — считай, что состав неизвестен, и дозапускай ревью целиком. 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 как есть, вынеси это в доклад как нераспознанное пересечение; - **дерево сабагента обязано быть чистым.** `git -C status --porcelain` до `rebase`: непусто — значит сабагент не довёл шаг 11 до коммита учёта (или оставил мусор). Не форсируй и не коммить за него: назови задачу в докладе недоведённой и оставь ветку с worktree. Молчаливый `rebase` на грязном дереве всё равно откажет, но с сообщением про unstaged changes — а причина другая; - **ненулевой код `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**, все разом — граф здесь плоский: машину эти проходы не держат, ребра между ними нет, а сама машина к этому моменту свободна (все сабагенты вернулись). Линейно — только по причине из раздела «Порядок прогона» `av-dev-pipeline:review-pipeline`, и причину назови; - **задание у этих проходов особое, и это надо сказать прямо.** Живого change здесь нет — все заархивированы, дельта-спек не существует. Источник требований — **актуальные** `openspec/specs//spec.md`, а предмет — стык: требование, которое одна задача выполнила, а соседняя незаметно отменила; два архивных change, по-разному описавшие одно поведение. Скажи проходу это прямо, иначе он пойдёт искать дельты и не найдёт ничего; - если задачи пересекались по файлам, добавь один `review-architecture` на интегрированный дифф с вопросом «не появился ли второй способ делать то, что уже делается» — именно он возникает, когда две задачи независимо решали похожее; - **заверши триажем.** Он единственный, кто агрегирует, и без него у находок нет ни оракула, ни пометки `инлайн`/`развилка` — а следующий абзац на неё опирается. Прогон из двух проходов без триажа — это сырые находки, выданные за разобранные. Граф этой сверки — веер в один сток, и он такой же, как у обычного прогона: ```mermaid flowchart TD merged["основная ветка после всех интеграций
(финальный гейт зелёный)"] s1["review-specs: capability 1
режим «стык после слияния»"] s2["review-specs: capability 2
режим «стык после слияния»"] arch["review-architecture на интегрированном диффе
(если задачи пересекались по файлам)"] tri["review-triage — сток"] merged --> s1 --> tri merged --> s2 --> tri merged --> arch --> tri ``` Замечания отрабатывай как одиночный пайплайн: `инлайн` чини сам, `развилка` — вопросом в запись; после правок — снова гейт. ### 9. Прибраться и доложить - Убери worktree и ветки **только успешно влитых** задач, в конце `git worktree prune`. Worktree и ветки **провалившихся** не трогай — они нужны для ручного дожатия. - **Записей учёта батч не трогает** — их правит пайплайн внутри сабагента на шаге 11. Батч сообщает исход по каждой задаче; если какой-то сабагент дошёл до коммита, но закрытия не сделал (плагина нет, вызов не разрешился), скажи это строкой — иначе задача останется открытой молча. - Доложи кратко: - **исход по каждой задаче** одним из трёх слов, с хешем коммита; - **режим прогона** — по одной или волнами, и если волнами, то по чьей просьбе; порядок задач или состав волн, порядок интеграции, с пометкой, какие задачи шли по одной как замеряющие; - вопросы, записанные сабагентами, пачкой; - что дозапускалось на шаге 5 и почему; шло ли где-то ревью инлайн; - итог финальной сверки и ссылки на архивные change; - **`Урожай`** — отложенные находки всех задач одним списком, с провенансом. Задачи из него заводит тот, кто ведёт задачи проекта, а не батч; - **отдельно — провалившиеся** задачи с настоящей причиной и путём к оставленному worktree; - **границы покрытия сводной строкой**, включая задачи, чьи замеры снимались в общей волне, и ветки, где ревью шло инлайн. ## Тонкости - **Изоляция параллельных тестов — цена параллельного режима, и проверяется она до первой волны.** Прежде чем гнать несколько прогонов разом, убедись, что тесты не делят фиксированный порт или файл БД (обычно берут временный каталог и эфемерный порт — тогда ок). Делят — такие задачи гони по одной, даже если просили параллельно, и скажи об этом строкой: просьба про параллельность, а не про сломанные тесты. В умолчательном режиме вопрос не встаёт вовсе — это одна из причин, по которым умолчание такое. - Поведенческая верификация внутри сабагента поднимает изменение вживую: в параллельном режиме следи, чтобы соседние worktree не дрались за порты и рабочие каталоги. Если проект умеет поднимать только один экземпляр — такие задачи в одну волну не ставь. - Ревью выполненного — **до** интеграции; это забота `av-dev-pipeline:task-pipeline` внутри каждого сабагента, дублировать не надо. - `openspec validate --strict` тоже внутри пайплайна задачи — не пропускай его своими правками на интеграции. - Крупная переработка, предложенная ревью внутри задачи, — развилка: не вливай молча, вынеси в доклад. - Держи вызывающего в цикле короткими репликами на переходах фаз (план → прогон задач → интеграция → финальная сверка), но не проси подтверждать механику.