Два независимых сабагента на av-dev-pm и av-dev-pipeline. Две находки нашли оба. Главная — моя же перестановка закрытия за коммит сломала reopen и батч. close печатал «дорога назад из git», а reopen искал коммит удаления, которого в новом порядке ещё нет: шаг 11 последний, учёт остаётся незакоммиченным. Проверено прогоном — отказ кодом 2 на свежезакрытой задаче. Тем же грязным деревом ломались rebase и worktree remove в батче: каждая закрывшая задачу ветка уехала бы в провалившиеся. Починено с обеих сторон: reopen берёт текст из HEAD, если коммита удаления нет, а шаг 11 коммитит учёт вторым коммитом. Вторая — канонический пример docs/.pm.json убивал tasks.py. Четыре документа показывали ключ tasks.sections, которого скрипт не знает: неизвестный ключ это код 3 на любой команде. Проект, заведённый по канону дословно, остался бы без работы с задачами, а docs.py при этом печатал «канон соблюдён». Секции живут в заголовках индекса и второго дома не получают. Остальные восемнадцать: init писал конфиг в упразднённый .tasks.json; looks_like_tasks не видел переименованный индекс; урожай спринта терял автотег после sprint close; ответ на вопрос по инструкции оставлял задачу незабираемой; adopt требовал недостижимого зелёного; путь отчёта триажа не переживал archive; review-specs не имел режима для стыка после слияния; три остатка «шаг 9а» несли предкоммитную позицию закрытия; sprint.md отрицал сам себя в пункте «Сделана». Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
33 KiB
name, description
| name | description |
|---|---|
| task-batch | Проводит несколько задач разом — планирует порядок и пересечения, гонит каждую задачу отдельным сабагентом в своём 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-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. Ветки после вливания удаляются.
Модель исполнения
- Каждая задача = один автономный сабагент (
general-purpose, чтобы иметь доступ к Skill и Agent для вложенных чекпоинтов ревью), работающий только в своём worktree и прогоняющийtask-pipelineцеликом на этой задаче. - Оркестратор кода задач не пишет: он планирует, заводит worktree, запускает сабагентов, проверяет полноту их ревью, интегрирует ветки и делает финальную сверку.
- Стиль правок внутри — заточка под проект и конвенции, right-size, без золочения.
Шаги
1. Прочитать набор
Набор задан списком (слаги, файлы, описания) — прочитай файл каждой задачи и
связанные спеки и черновики. Задачи-идеи включаются, но помни: сабагент проведёт
их сперва через opsx:explore, это тяжелее и чаще упирается в вопрос.
2. Спланировать порядок и пересечения (автономно)
Для каждой задачи определи:
-
затронутые capability — по её описанию и по каталогу актуальных спек (
openspec/specs/); -
жёсткие зависимости: задача B строится на результате A → A строго раньше B;
-
замеряющая задача — та, чьё ревью будет доказывать находки числами, и потому она гонится в волне одна (обоснование — ниже, в шаге 4). Решается здесь, на планировании, а не во время прогона: состав волны определяется сейчас, а профиль ревью сабагент выберет только внутри задачи, и ключевать волну на ещё не сделанный выбор нельзя. Триггеры — по фактам о задаче, каждый сам по себе достаточен:
- трогает схему хранилища, миграцию, формат на диске или объём хранимого;
- трогает конкурентность: транзакции, блокировки, фоновые циклы, общее состояние;
- трогает размер тела, буфер, память, сжатие, ретеншен, темп потока;
- её тема названа в
docs/research/или в журналеdocs/review.mdкак место, где уже мерили или уже ломалось.
Ни один триггер не сработал — задача не замеряющая, даже если её ревью окажется
deep.deepпро глубину проверки, замеряющая — про соревнование за железо; это разные вопросы, и совпадают они не всегда; -
нумерованные артефакты — номера раздаёт оркестратор заранее. Если проект нумерует миграции (путь —
docs/.pm.json, ключmigrations), посмотри последний номер и раздай номера всем задачам, которые, вероятно, их добавят, до запуска. Номер уходит в charter сабагента, и он берёт назначенный, а не «следующий свободный».Это отдельная механика от правила волны, и она ему не служит — их раньше путали, и они тянули в разные стороны. Правило волны отвечает на вопрос «кто с кем гонится одновременно», предраздача — на вопрос «какой номер берёт задача». Раздача нужна там, где две задачи одной под-пачки добавляют нумерованный артефакт: каждая считает «следующий свободный» по основной ветке, которая ещё не видела соседку, и обе берут один номер. Миграции под это почти не попадают — миграция и так триггер замеряющей задачи, а замеряющая идёт одна; но нумерованные артефакты бывают не только миграциями. Поэтому номера раздаются всем задачам с таким артефактом, независимо от того, в какой волне они окажутся: раздача ничего не стоит, а её отсутствие ловится только конфликтом на интеграции. Между волнами проблемы нет — ветка следующей волны берётся от вершины, уже включающей предыдущие;
-
жёстко сериализуем (не гоняем одновременно) настоящие пересечения:
- одна capability на несколько задач — две задачи, правящие одну спеку (тем
более одно и то же
### Requirement), дают не текстовый, а семантический конфликт при архивации; сериализуем по смыслу, а не только по файлам; - пересечение по одним и тем же исходникам;
- одна capability на несколько задач — две задачи, правящие одну спеку (тем
более одно и то же
-
мягкие конфликты сериализовать не надо: файлы-перечни, где каждая задача правит свою строку (индексы, оглавления, списки записей), и спеки разных capability — разные строки и файлы, сливаются сами.
Собери план: волны параллельно-безопасных задач плюс сериализованный хвост конфликтоопасных, с учётом зависимостей; замеряющие задачи стоят в плане отдельными волнами по одной. Покажи план короткой репликой — назвав, какие задачи признаны замеряющими и по какому триггеру, — и иди дальше.
3. Свежая база
Убедись, что рабочее дерево чистое и основная ветка свежая. Зафиксируй базовый коммит. Новые ветки бери от свежей вершины; ветки следующей волны — от вершины, уже включающей результат предыдущих волн.
4. Прогнать волны
Потолок параллелизма — 2–3 задачи одновременно. Каждая задача тянет полный цикл пайплайна с вложенным ревью и гейтом, поэтому больше трёх разом душат машину и провоцируют гонки. Волну шире трёх бей на под-пачки по ≤3 и гони под-пачки последовательно: следующая стартует, когда предыдущая вернула отчёты. Иначе потолок обходится тривиально — шесть задач, запущенных «двумя под-пачками» в одном сообщении, это шесть задач разом.
Волна из одной задачи — не вырожденный случай, а обязательный. Задача, признанная на шаге 2 замеряющей, гонится в волне одна: соседний прогон на той же машине портит числа, а находка с испорченным оракулом хуже отсутствующей — она выглядит доказанной. Если замеряющая задача всё же пошла в общей волне, её отчёт обязан нести строку в границах покрытия: замеры сняты под соседней нагрузкой.
Для каждой задачи в под-пачке:
- Заведи worktree и ветку от текущей вершины:
git worktree add <path> -b task/<slug> <основная ветка>. Путь — во временном каталоге проекта (./tmp), не в системном/tmp. - Запусти по одному сабагенту на задачу, все в одном сообщении,
subagent_type: general-purpose. Charter сабагента:- работай строго в своём worktree
<path>; в другие каталоги и в основную ветку не лезь; - прогони Skill
av-dev-pipeline:task-pipelineровно на этой задаче, полный цикл SDD с обоими чекпоинтами ревью; - если задаче назначен номер артефакта — используй строго его;
- профиль ревью выбирается по факту изменения. Батч не повод понижать профиль: «нас много и мы спешим» — это ровно тот стимул, из-за которого проходы пропускают;
- режим прогона проходов — последовательный. Твой worktree не один на машине;
- если вложенные сабагенты недоступны (движок не даёт запускать агентов из
агента) — не пропускай ревью и не понижай профиль: проведи его инлайн по
тем же charter'ам
av-dev-pipeline, сохранив обязательное — гейт до опиниативных проходов, состав по профилю, триаж последним. И скажи в отчёте прямым текстом, что ревью шло инлайн: инлайновый проход видит контекст автора и потому декоррелирован слабее — это меняет доверие к результату, а не только способ запуска; - вернуть отчёт, в котором обязательно: исход задачи одним из трёх слов;
объявленный профиль ревью и режим прогона; что сделано; какие вопросы
записаны и куда; изменённые файлы; добавлялся ли нумерованный артефакт и с
каким номером; затронутые capability; состояние гейта; перечень
запущенных проходов ревью поимённо с исходом каждого; путь к
сохранённому отчёту триажа (
openspec/changes/<id>/review/, после архивации —openspec/changes/archive/<id>/review/); шло ли ревью инлайн; границы покрытия.
- работай строго в своём worktree
Сабагент, упершийся в вопрос, не останавливает батч: он записывает вопрос, режет задачу до остатка и доводит остаток — либо, если остатка нет, возвращает исход «не доведена». Оркестратор собирает такие вопросы и выносит их в финальный доклад пачкой.
5. Проверить полноту ревью — до интеграции
Ветка, чей отчёт не называет профиль и проходы поимённо, не вливается. Пропуск прохода не отличим от прохода без находок, и на уровне батча это ещё опаснее: отчётов много, каждый выглядит полным, а сверять их некому, кроме тебя.
Сверка идёт в три шага, и порядок важен:
- Возьми объявленный профиль из отчёта задачи — он затем и заказан в обязательных полях шага 4. Профиля в отчёте нет — перечень проходов сверять не с чем; это само по себе основание не вливать, пока сабагент не назовёт профиль и не обоснует его по факту изменения.
- Сверяй с независимым артефактом, а не с прозой отчёта. Перечень проходов
бери из сохранённого отчёта триажа (
openspec/changes/<id>/review/илиopenspec/changes/archive/<id>/review/— задача доведена, change заархивирован) — пайплайн обязан его туда положить. Проза сабагента написана тем же, кто мог проход и пропустить: она подтверждает сама себя. Отчёта триажа на месте нет — считай, что состав неизвестен, и дозапускай ревью целиком. - Сверь состав с таблицей профилей скилла
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/<slug>
checkout'нута в нём, и git rebase <основная> task/<slug> из главного worktree
падает: fatal: 'task/<slug>' is already used by worktree at …. Поэтому
rebase делается внутри worktree задачи, а ff-слияние — из главного.
Для каждой готовой ветки task/<slug>:
git -C <path> rebase <основная>— перенос ветки задачи на текущую вершину, выполняется в её собственном worktree;- резолв конфликтов (их почти нет — конфликтоопасное сериализовано, номера
розданы заранее). Неавтоматический конфликт — не форсируй: прерви
(
git -C <path> rebase --abort), оставь ветку и worktree как есть, вынеси это в доклад как нераспознанное пересечение; - дерево сабагента обязано быть чистым.
git -C <path> status --porcelainдоrebase: непусто — значит сабагент не довёл шаг 11 до коммита учёта (или оставил мусор). Не форсируй и не коммить за него: назови задачу в докладе недоведённой и оставь ветку с worktree. Молчаливыйrebaseна грязном дереве всё равно откажет, но с сообщением про unstaged changes — а причина другая; - ненулевой код
rebaseотносится к этой ветке и только к ней. Прерванный rebase в чужом worktree не трогает ни главное дерево, ни остальные ветки: проверьgit -C <path> statusиgit status— обе чистые. Уводить весь батч в провалившиеся из-за одного ненулевого кода запрещено: это ложная причина, из-за которой зелёные задачи не доедут до основной ветки. Провалилась одна — провалилась одна; - из главного worktree (он стоит на основной ветке — проверь
git rev-parse --abbrev-ref HEAD):git merge --ff-only task/<slug>. Ветку в главном дереве не переключай —git checkout task/<slug>тоже упрётся в занятость; - после каждой интеграции — гейт на основной ветке. Красное — откати эту
интеграцию (
git reset --hardна прошлую вершину), ветку с worktree сохрани, задачу перечисли в докладе. Основная ветка никогда не остаётся полузелёной; - только после зелёного:
git worktree remove <path>иgit branch -d task/<slug>— в этом порядке, иначе ветка снова занята.
Политика частичного провала. Упавшая задача (исход «не доведена», красные тесты в её worktree, конфликт при rebase, невлитая из-за находок дозапуска) не блокирует остальные: интегрируем все зелёные, упавшую оставляем в её worktree и ветке нетронутой — ничего не удаляем, — и перечисляем в докладе с причиной, отчётом и путём к worktree. Причина называется настоящая: «конфликт rebase в файле X», а не «нераспознанное пересечение» на всякий случай.
7. Финальный гейт
На основной ветке после всех интеграций — гейт целиком. Зелёное обязательно; пока красное, шаг 8 не начинается.
8. Финальная сверка — только то, чего не видел никто
Каждая задача уже прошла полный конвейер в своём worktree. Повторять его на интегрированном диффе бессмысленно: те же проходы на тех же файлах дадут те же находки и удорожат триаж. Здесь проверяется только то, что появилось от слияния:
- запусти по одному
review-specsна каждую затронутую capability, последовательно. Параллельно — только если человек попросил явно и назвал набор: правило и оба его условия живут вav-dev-pipeline:review-pipeline, раздел «Режим запуска», и здесь не ослабляются. «Замеров не делают» основанием не является; - режим у этих проходов особый, и его надо назвать в задании. Живого change
здесь нет — все заархивированы, дельта-спек не существует. Источник требований
— актуальные
openspec/specs/<capability>/spec.md, а предмет — стык: требование, которое одна задача выполнила, а соседняя незаметно отменила; два архивных change, по-разному описавшие одно поведение. Скажи проходу это прямо, иначе он пойдёт искать дельты и не найдёт ничего; - если задачи пересекались по файлам, добавь один
review-architectureна интегрированный дифф с вопросом «не появился ли второй способ делать то, что уже делается» — именно он возникает, когда две задачи независимо решали похожее; - заверши триажем. Он единственный, кто агрегирует, и без него у находок нет
ни оракула, ни пометки
инлайн/развилка— а следующий абзац на неё опирается. Прогон из двух проходов без триажа — это сырые находки, выданные за разобранные.
Замечания отрабатывай как одиночный пайплайн: инлайн чини сам, развилка —
вопросом в запись; после правок — снова гейт.
9. Прибраться и доложить
- Убери worktree и ветки только успешно влитых задач, в конце
git worktree prune. Worktree и ветки провалившихся не трогай — они нужны для ручного дожатия. - Записей учёта батч не трогает — их правит пайплайн внутри сабагента на шаге 11. Батч сообщает исход по каждой задаче; если какой-то сабагент дошёл до коммита, но закрытия не сделал (плагина нет, вызов не разрешился), скажи это строкой — иначе задача останется открытой молча.
- Доложи кратко:
- исход по каждой задаче одним из трёх слов, с хешем коммита;
- план волн и порядок интеграции, с пометкой, какие задачи шли по одной как замеряющие;
- вопросы, записанные сабагентами, пачкой;
- что дозапускалось на шаге 5 и почему; шло ли где-то ревью инлайн;
- итог финальной сверки и ссылки на архивные change;
Урожай— отложенные находки всех задач одним списком, с провенансом. Задачи из него заводит тот, кто ведёт задачи проекта, а не батч;- отдельно — провалившиеся задачи с настоящей причиной и путём к оставленному worktree;
- границы покрытия сводной строкой, включая задачи, чьи замеры снимались в общей волне, и ветки, где ревью шло инлайн.
Тонкости
- Изоляция параллельных тестов. Прежде чем гнать несколько прогонов разом, убедись, что тесты не делят фиксированный порт или файл БД (обычно берут временный каталог и эфемерный порт — тогда ок). Делят — гони такие задачи последовательно.
- Поведенческая верификация внутри сабагента поднимает изменение вживую: следи, чтобы соседние worktree не дрались за порты и рабочие каталоги. Если проект умеет поднимать только один экземпляр — такие задачи в одну волну не ставь.
- Ревью выполненного — до интеграции; это забота
av-dev-pipeline:task-pipelineвнутри каждого сабагента, дублировать не надо. openspec validate --strictтоже внутри пайплайна задачи — не пропускай его своими правками на интеграции.- Крупная переработка, предложенная ревью внутри задачи, — развилка: не вливай молча, вынеси в доклад.
- Держи вызывающего в цикле короткими репликами на переходах фаз (план → волны → интеграция → финальная сверка), но не проси подтверждать механику.