Цель была обязательной во всех трёх местах сразу: sprint start требовал слаг, check считал ошибкой набор без названной цели, sprint take отказывал задаче под чужой целью. Модель описывала только спринт развития — а спринт бывает под багфикс, под техдолг, под здоровье проекта: такой набор собран по работоспособности, а не по направлению, и цели у него нет не по недосмотру. Обходной путь существовал и был хуже прямого: завести цель-пустышку «Здоровье проекта» и вешать под неё fix-ы. Тогда ROADMAP.md — документ про то, что приложение умеет, — обрастает строками про то, что оно не ломается, а тег goal: перестаёт значить направление. Теперь sprint start принимает --goal <слаг> ИЛИ --no-goal, и голое отсутствие обоих — отказ с объяснением. Причина в стимуле: цель называет человек, и это единственный продуктовый вопрос всей сессии. Разреши мы заводить спринт просто без флага — забытый флаг, лень спросить и осознанное решение стали бы неотличимы на выходе, а дешевле всего из трёх агенту именно не спрашивать. Тот же обход записан в «Стимулы» скилла session вместе с защитой. В спринте без цели цель не проверяется вовсе: набор берёт что угодно готовое к взятию, включая задачи под разными целями. Правило «набор служит одной цели» не ослаблено, оно просто не применяется — целей там не больше одной, их ноль. Взамен машинной проверки остаётся показ набора человеку до заморозки, и в cadence.md сказано прямо: у бесцельного спринта это единственная проверка состава. Доклад обязан называть спринт бесцельным и объяснять, чем он был. Признак «спринт идёт» разъехался с целью и переехал на слаг: слаг есть у любого спринта, потому что без него нечем проставить sprint:<слаг>, то есть нечем собрать урожай. На sprint_started() переведены sprint take, блок здоровья check и reopen. Поле «Цель» в шапке остаётся и у бесцельного набора — пишется прозой без ссылки: «цели нет» и «цель потерялась» обязаны различаться. Побочно выправлен reopen: он возвращает задачу в набор идущего спринта тем же тестом, что и sprint take (мешает только чужая цель). Прежде тест был уже — совпадение целей, — и задача без цели, реопенутая при спринте с целью, уезжала в беклог вопреки прозе скилла. Тема 39 в DECISIONS.md, следствия 145-147. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
17 KiB
Ведение спринта
Спринт — набор задач, замороженный до его конца: под одну цель или без цели
(багфикс, техдолг, здоровье — это законно, sprint start --no-goal). Здесь то,
что происходит внутри спринта: как задача заканчивается, что считается сделанным,
кто принимает и что идёт в доклад. Как спринт набирается — шаг 4 в
cadence.md.
Наблюдаемые исходы задачи
Как они достигаются — дело пайплайна проекта. Сессия знает только исход и его след.
- Сделана — по определению готовности ниже.
close <slug> --implemented: файл и строка удаляются, следом остаётся коммит. Закрывает агент-оркестратор последним шагом пайплайна, после коммита; приёмка человеком идёт позже и отменяетсяreopen— см. «Кто и когда закрывает». - Вышла из спринта —
sprint drop <slug> --reason …: возвращается в беклог с вопросом в файле и без живого незакоммиченного предложения — иначе при следующем взятии оно столкнётся с новым. Наработки, которые жалко терять, переезжают в тело задачи текстом. - Оказалась крупнее задачи — распознаётся до того, как под неё заведено предложение об изменении, иначе его придётся выбрасывать. Выходит из набора, уходит на декомпозицию; спринт продолжается остальными, части заводятся под той же целью (у спринта без цели — без неё) и в замороженный набор не добавляются.
- Отменена решением по ходу —
close <slug> --reason "<ссылка на решение>"прямо из спринта. Это редкий, но законный исход, и он называется в докладе.
Конец спринта — когда по каждой задаче набора наступил один из исходов. Не
«все сделаны»: иначе одна застрявшая задача держит спринт бесконечно. Затем
sprint close.
flowchart TD
take["sprint take — задача в наборе"]
done["сделана<br/>close --implemented"]
out["вышла<br/>sprint drop --reason"]
epic["крупнее задачи<br/>распознаётся до заведения change"]
cancel["отменена решением по ходу<br/>close --reason"]
all{"по каждой задаче набора<br/>наступил исход?"}
harvest["урожай заводится интейком tasks"]
close["sprint close"]
dissolve["sprint close --dissolve --reason<br/>недоделанное — в беклог"]
take --> done
take --> out
take --> epic
take --> cancel
done --> all
out --> all
epic --> all
cancel --> all
all -->|да| harvest
harvest -->|"тег sprint: ставится, пока SPRINT.md не очищен"| close
take -->|"продолжать нечем ни одной задачей — блокер"| dissolve
done -->|"приёмка не сошлась: reopen --reason"| take
Два ребра на схеме — те, где порядок обязателен и нарушается молча: урожай до
sprint close (после команды автотег уже не поставится) и блокер в обход
исходов (спринт распускается, а не ждёт).
Схема — сводка: определение готовности и правила приёмки ниже, и при расхождении прав текст.
Урожай заводится при закрытии спринта, а не при закрытии задачи. Это
обязанность закрывающего: пройти по спискам находок от исполнителей и завести
недостающее интейком скилла tasks — с дедупликацией и картой человеку. Заводимое
метится тегом спринта само (sprint:<слаг>), поэтому первая порция следующей
сессии поднимается одной командой list --tag sprint:<слаг>. Спринт, закрытый
без этого шага, оставляет находки жить в отчётах — то есть нигде.
Порядок здесь обязателен: урожай заводится ДО команды sprint close.
Автотег ставится по слагу из SPRINT.md, а sprint close этот файл очищает;
заведённое после команды остаётся без тега и в первую порцию следующей сессии
не попадёт — молча, потому что пустой list --tag выглядит как «урожая не
было». Если так уже вышло, тег ставится руками: add … --tag sprint:<слаг>,
слаг берётся из отчёта sprint close.
Провал спринта. Сработал блокер — спринт распускается (sprint close --dissolve --reason …), недоделанное возвращается в беклог, новый набор
делается после ответа человека. Спринт не «ждёт»: ждать может человек, а
замороженный набор, который нельзя двигать, только мешает.
Протухший набор — второй законный повод роспуска. Работа стояла, и человек
вернулся к спринту, состав которого уже не держит в голове. Тем же роспуском:
sprint close --dissolve --reason "работа стояла с <когда>", недоделанное в
беклог, новый набор — после переоценки, а не поверх старого.
Порога в неделях нет и не будет: счётчик простоя пришлось бы вести руками, а решает всё равно человек. Признак — не срок, а что набор перестал быть твоим: взялся перечитывать, зачем эти задачи вместе, — он протух. Заморозка тут не мешает, она запрещает двигать набор, а не распустить его целиком.
Определение готовности
Задача засчитывается сделанной, когда верно всё:
- Пайплайн задачи пройден до конца — со своим определением готовности, за
которое отвечает проект: проверки, состав ревью, документация, коммит. Здесь
оно не пересказывается и не подменяется — форма фиксирована, содержание
даёт
CLAUDE.mdпроекта. Пайплайна нет, задача сделана руками — условие читается как «проверки проекта зелёные и изменение влито». - Критерии приёмки проверены поимённо — каждый со своим оракулом, исход по каждому назван. Это единственное, что добавляет управление задачами: пайплайн отвечает «сделано по правилам», критерии — «сделано то, что заказывали».
- Находки по ходу отданы списком — исполнитель обязан их назвать (каждую, с пометкой «заведена / не заведена: причина»), но не обязан заводить: заведение интерактивно — оно требует дедупликации против беклога и кладбища, а ещё решений человека. Обязанность завести урожай — на закрытии спринта, ниже. Иначе автономный исполнитель оказался бы разом и обязан завести задачи, и не вправе сделать это в одиночку.
Кто и когда закрывает
Задачу закрывает агент-оркестратор — тот же, кто её и сделал, последним шагом пайплайна, после коммита. Порядок:
- пайплайн доводит задачу до коммита;
- после коммита зовёт
Skill av-dev-pm:tasksи закрывает задачу (close <slug> --implemented); строка уходит изSPRINT.md; - докладывает исход и по каждому критерию — оракул и наблюдаемый исход. Это доклад приёмщику, а не отметка «принято».
Приёмщик и исполнитель здесь совпадают, и это принято сознательно — цена
названа в SKILL.md, раздел «Стимулы». Поэтому закрытие не окончательно, а
доклад по критериям — не формальность: он единственное, по чему приёмка вообще
возможна.
Порядок «коммит, потом закрытие» обязателен. Закрытие удаляет файл задачи; упавший коммит после закрытия оставил бы задачу закрытой без единого следа работы.
Само закрытие тоже коммитится, отдельным коммитом. Удаление файла задачи и
правка индекса — правки в рабочем дереве; пока они не в истории, SPRINT.md
ничего не показывает, а reopen восстанавливает текст из HEAD в узком окне:
его закроет первый посторонний коммит. Сообщение про учёт, а не про
работу: закрыта задача <slug>.
Дорога назад существует и обязана быть названа. Человек на сессии сверил
критерии, и приёмка не сошлась — tasks.py reopen <slug> --reason "приёмка не сошлась: …":
файл восстанавливается из истории git, строка возвращается в набор идущего
спринта (или в беклог, если спринта нет), строка кладбища снимается. Тело
восстанавливается на момент удаления — всё, что было дописано позже, живёт
только в коммите задачи, и это называется в докладе.
Кто и по чему принимает
Три условия, без которых пункт про критерии не исполняется никем:
- Критерии переживают файл задачи. Файл удаляется при закрытии, поэтому
критерии копируются туда, где их увидит приёмщик. Куда именно — отвечает
пайплайн проекта, а не слот в
CLAUDE.md: он переносит их вtasks.mdизменения, когда заводит change. Проект без пайплайна называет своё место сам. - Принимает человек на сессии, а не отдельный агент. Исполнитель и приёмщик
в момент закрытия не разведены (решение о снятии и его
цена — в
SKILL.md, «Стимулы»). Опоры остались три: сохранённый независимый отчёт ревью (при конвейереav-dev-pipeline— отчёт триажа вopenspec/changes/archive/<id>/review/, до архивации —changes/<id>/review/),SPRINT.mdпод git иreopen. Переоценка на сессии и есть момент, когда критерии видит не исполнитель. Конвейера ревью в проекте нет — первой опоры нет тоже, и это называется строкой доклада, а не обходится молча (SKILL.md, «Стимулы»). - Расхождение — дефект критериев. Приёмщик правит критерии и возвращает задачу исполнителю в этом же спринте: ответ есть, остаток есть, по тесту про остаток это не выход из спринта.
Что врывается в замороженный набор
Только два класса — правило и его обоснование в SKILL.md. Здесь механика:
- вторжение не добавляет задачу в набор:
SPRINT.mdостаётся тем набором, который заморозили и показали. Внеплановая работа делается и называется в докладе отдельной строкой «внеплановое: что и почему»; - если внеплановое требует больше пары часов, честнее распустить спринт, чем делать вид, что набор соблюдается;
- всё остальное падает в беклог через обычный интейк и ждёт сессии.
Доклад в конце спринта
Проверяемые якоря, а не пересказ:
- Цель спринта — или строка «спринт без цели» с тем, чем он был (багфикс, техдолг, здоровье): у бесцельного набора это единственное место, где состав вообще объясняется. И по каждой задаче набора: хеш коммита, дословный исход проверок проекта, исход по каждому критерию приёмки.
- Какие развилки решались и чем обоснованы.
- Урожай: сколько задач заведено, какие вопросы накопились, что вышло из спринта и почему, что было внеплановым.
- Поимённая сверка урожая с независимыми отчётами ревью: каждая отложенная находка имеет либо слаг, либо строку «не заведена: причина». Нулевой урожай при непустом отчёте — сигнал, а не благополучие. Отчётов нет (проект без конвейера ревью) — сверять не с чем, и строка доклада говорит именно это, а не «сверено».
- Созрела ли порция для сессии. Решение звать — человека, напоминание —
обязанность агента:
⌈урожай / 8⌉порций. - Границы покрытия сжатой строкой: что в этом спринте не проверялось вовсе.