Files
dev-skills/av-dev-pm/skills/session/references/cadence.md
T
avandClaude Opus 5 37394444e8 спринт без цели: цель стала необязательной, но не молчаливой
Цель была обязательной во всех трёх местах сразу: 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>
2026-08-07 09:19:29 +03:00

278 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Сессия: четыре шага
Одна сессия между спринтами. Порядок шагов — **зависимость, а не список**:
переоценивать задачи, не разобрав вопросы, значит переоценивать вслепую; набирать
спринт, не переоценив, значит набирать из протухшего.
Начинается сессия с `tasks.py check``check --fix`, если дрейф накопился) —
результат идёт строкой в доклад.
## Шаг 1. Разбор вопросов
`tasks.py list --questions` — всё, что накопилось. Вопрос это решение человека,
и разбирается он **пачкой**, а не по одному, как только возник: по одному —
это дёрганье, пачкой — это сессия.
Порядок по каждому вопросу:
1. **Проверь, не отвечен ли он уже** — решением, документом, соседним
изменением, самим ходом прошедшего спринта. Отвеченный вопрос не выносится
человеку: это самая частая находка и она не требует ничьего решения.
2. **Сформулируй развилку** с вариантами и последствием каждого, рекомендация —
первым вариантом.
3. **Вынеси пачкой** через `AskUserQuestion`, не больше трёх за раз.
4. **Запиши ответ в тело задачи, опустоши раздел «Вопросы»**, сними тег
(`edit <slug> --rm-tag question`), **перепиши «зачем»**: «Решено:
…» на вопрос «почему это лежит в беклоге» уже не отвечает. Опустошение
раздела — не уборка, а условие взятия: правило и причина в скилле `tasks`,
[references/task-format.md](../../tasks/references/task-format.md).
**Вопросы на задачах-кандидатах разбираются вне очереди порции** — здесь же, на
этой сессии, даже если сама задача в порцию переоценки не попала. Иначе правило
«задача с открытым вопросом в набор не берётся» создаёт стимул вопрос не
записывать, лишь бы не вычеркнуть задачу из ближайшего спринта.
## Шаг 2. Разбор прошедшего спринта — про процесс, а не про задачи
Не «что мы сделали» (это доклад спринта, он уже был), а:
- **что сломалось в процессе и почему не поймали** — промах, доехавший до конца;
- **что оказалось дороже, чем выглядело при заведении** — не число, а сам факт и
причина: чего не было видно в постановке;
- **какие правила не сработали или сработали не так** — в том числе правила
этого плагина.
Замеров процесс не ведёт намеренно: оценки в очках и velocity не взяты
(«[Почему не Scrum](../SKILL.md#почему-не-scrum)»), а спринт ограничен объёмом, а
не временем — сравнивать «сколько заняло» не с чем. Разбор здесь качественный, и
это не упущение.
**Артефакт обязателен.** Вывод, оставшийся в контексте сессии, не существует:
следующая сессия его не увидит. Дом у него один и известен из канона —
**`docs/review.md`**: вывод про конвейер и про то, что перестали проверять, идёт
в раздел настройки, вывод про воспроизведённый дефект — в журнал. Решение с
долгим следом — в `docs/adr/`.
Отдельным ритуалом ретроспектива не выделяется: процесс личный,
синхронизировать некого.
**Здесь же зовутся оба судьи документов** — на весь канон разом, а не на пачку,
отобранную работой:
- **`doc-consistency`** — согласованность документов между собой и с openspec:
факт в двух домах, прямое противоречие, поведение в `architecture.md` вместо
спек, ADR без парного статуса при замене, число без провенанса;
- **`doc-code-drift`** — сверка с кодом по закрытому перечню фактов: имя основной
ветки, команды, пути, внешние зависимости поимённо, настройки с числовым
значением, единые точки проекта, capability.
Раз в спринт, а не чаще. Дорог из них по-настоящему первый — `doc-consistency`
на `opus`: он сличает утверждения двух документов, и это суждение. Второй с
недавних пор на `sonnet` — у него закрытый перечень фактов и команда на каждый, —
но он читает репозиторий целиком, и дешёвым от смены модели не стал. Но и не
реже — **спринт это ровно то, что двигает код и документы**:
переименованная цель сборки, ушедшая зависимость, второй способ делать то, что
обзор объявил единственным; факт, дописанный в `architecture.md`, уже живущий в
`CLAUDE.md`. Протухшее и раздвоившееся неотличимо от свежего, и по нему принимают
решения, пока кто-нибудь не наткнётся.
**Пачка — весь канон, и это не расточительство, а охват.** Когда пачку отбирала
работа, без присмотра оставалось ровно то, чего работа не касалась: правка,
отменившая решение, живёт в одном документе, а парный статус нужен в другом.
Канон мал, раз в спринт он читается целиком.
Находки обоих — обычный материал переоценки: строка на замену идёт в документ
сразу, работа больше чем на абзац становится задачей типа `chore`. **Позвал —
скажи в докладе, кого именно позвал, и приложи границы покрытия**; не позвал —
скажи и это, иначе доклад читается как «сверено».
## Шаг 3. Переоценка задач
Цель — выкинуть то, что перестало быть задачей, и вернуть остальному честное
состояние. Не «пересмотреть всё», а «пересмотреть порцию до конца».
### Порция и правило остановки
Тридцать задач за один заход — это усталость и штамповка: последние десять
получат «оставить» не потому, что живы, а потому, что сессия затянулась.
- **5–8 задач за порцию.** Размер обоснован усталостью, а не пропускной
способностью, и менять его не надо — **надо брать несколько порций за
сессию**.
- **Сколько порций:** не меньше `⌈урожай прошедшего спринта / 8⌉`. Урожай — это
задачи, заведённые за спринт; при урожае в 15 это две-три порции.
- **Отбор порций по порядку:**
1. **урожай спринта**`list --tag sprint:<слаг>`: свежезаведённое ещё не
проходило ни одной проверки на нужность. Тег на задачах проставлен
автоматически при заведении — руками не метят и не вспоминают. **Слаг
берётся из отчёта `sprint close`, а не из `SPRINT.md`:** сессия идёт после
закрытия, а закрытие этот файл очищает;
2. дальше **по залежалости**`list --stale`;
3. по потребности — одна секция целиком, один тег (партия ревью), одна цель
(`--goal`), список от пользователя.
- **Останавливайся на границе порции**, даже если «ещё чуть-чуть осталось».
Между порциями — промежуточный доклад.
### Что делать с каждой задачей
Сперва то, что не требует ничьего решения:
1. **Проверь, не сделано ли уже.** Задача, реализованная попутно в соседнем
изменении, — самая частая находка. Смотри код, документацию, историю коммитов
по ключевым словам. Удаление «как реализованной» деструктивно и без следа
`REJECTED.md` реализованные не пишутся), поэтому порог улики жёсткий:
`close <slug> --implemented` только имея **конкретный коммит или строку
документа**, закрывающие задачу, и ссылка идёт в доклад. Есть лишь косвенные
признаки — не удаляй сам, вынеси в пачку вопросов. Сделана частично → задача
сжимается до остатка: тело правишь редактором, заголовок и «зачем» — через
`edit`.
2. **Проверь, не отменена ли решением.** Документ, ADR или архивное изменение
мог закрыть вопрос иначе — тогда `close <slug> --reason "<ссылка на
решение>"`. Задача закрывается не только коммитом.
3. **Проверь пересечения.** Две задачи об одном — содержимое в одну, вторую
`close <slug> --reason "слита с <другой-слаг>"`. Смотри **шире порции**:
интейк дедуплицирует новое против существующего, но никогда не
пересматривает уже лежащее, и две задачи с одной причиной могут лежать рядом
месяцами.
4. **Пере-кластеризуй по общей причине.** Несколько задач, оказавшихся симптомами
одного дефекта, сливаются в одну — это находка, которую интейк дать не мог.
5. **Гигиена полей** — протухшее «зачем», вопрос в прозе, снятый ответ, свойство
репозитория в рамках, предписание процесса в теле, тип, разошедшийся с
задачей, границы вместо реализации в разделе «Затрагивает». Список и правила —
в скилле `tasks`. **Переоценка — то самое место, где беклог добирает тип и
разделы его схемы:** требовать их на входе значило бы выгонять в заметки то,
что должно лежать задачей, а к взятию в спринт они уже обязательны. Блок
здоровья `check` печатает, сколько записей готово к взятию, — по этому числу
и видно, добрала переоценка или нет.
Затем — то, что решает пользователь:
6. **Жива ли она вообще.** Контекст мог измениться: ушла зависимость, отпал
сценарий, обошли иначе. Здесь и звучит вопрос, выкидывать ли.
7. **Та ли цель — и нужна ли она вообще.** Приоритетов нет, и «повысить» нечего
— вместо повышения задача **меняет цель** (`edit <slug> --goal <другой>`) или
входит в ближайший набор. `feature`, которой не находится цель, — кандидат
на выход: новая возможность вне цели это возможность, которой никто не
заказывал. Операционной задаче (`fix`, `chore`, `research`) цель не нужна, и
выдумывать её здесь не надо.
**Отменяется и сама цель** — когда замысел оказался неверен, а не когда
задача выбрала не ту. Тогда порция расширяется до всех задач этой цели: каждую
либо закрыть своей причиной, либо перевесить на другую цель, и только потом
закрыть цель. Порядок и почему он такой —
[task-goal.md](../../tasks/references/task-goal.md#отменённая-цель--сперва-задачи-потом-цель).
Здесь этому и место: отмена цели это разбор её задач, а разбор задач — этот
шаг.
8. **Задача ли это по-прежнему.** Не проходит тест «готова к взятию» → `edit
<slug> --type research` и опустошённый раздел «Вопрос», то есть сырьё; дальше
штурм. Разрослась → это несколько задач под той же целью, дальше
декомпозиция.
9. **Переоценка по пройденному.** Прошедший спринт показывает, чего на самом
деле стоит такая работа. Это меняет цену **других** задач, и именно здесь
применяется: задача, оказавшаяся заметно дороже, чем думалось, при прежней
пользе — кандидат на выход. Судит человек по тому, что помнит о прошедшем
спринте; замеров процесс не ведёт и оценок не хранит.
### Храповик на залежавшихся
Сильно залежавшаяся задача — сигнал сама по себе: её либо ни разу не собирались
делать, либо нечем взять. Измеряй наблюдаемым — датой последней правки из git
(`list --stale` ставит такие первыми); счётчик «сколько сессий пережила» нигде
не хранится.
Задача из верхних строк `--stale`, которую и этот заход оставляет без изменений,
**либо двигается (меняет цель, идёт в набор, уходит с причиной), либо остаётся с
явно записанной причиной**, почему её держим (`move <slug> --section <та же>
--reason …`). Молчаливое «оставить как есть» на давно неподвижной задаче — это
решение не принимать решение; запись причины превращает его в осознанное и не
даёт тому же вопросу всплыть на следующей сессии.
### Интерактив
- Вопросы — через `AskUserQuestion`, **не больше трёх за раз**. Порция в 5–8
задач обычно даёт больше трёх суждений — тогда веди несколько итераций по ≤3,
а не по одному на задачу и не одним перегруженным запросом.
- К каждому варианту — **предварительное суждение, рекомендация первым
вариантом**: «предлагаю выкинуть, потому что …». Пользователю дешевле
возразить, чем судить с нуля.
- Всё, что решается фактом (сделано / отменено / дублируется), решай сам и
показывай списком в докладе, а не выноси в вопросы.
Пример одной итерации — три залежавшихся задачи, механику по ним уже разобрали:
> **Переоценка: 3 залежавшихся (порция по `--stale`)**
>
> 1. `versii-kachestvo-repaki` — версии и качество одного тайтла
> - Выкинуть *(рекомендую)* — помечена «не боль», за полгода ни разу не возникла
> - Оставить под целью `nadyozhnost-razdach`
> - Перевести под цель `kachestvo-mediateki` — там она первая в очереди
> 2. `backup-sqlite` — бэкап базы
> - Оставить под текущей целью *(рекомендую)* — не сработала, но риск реальный
> - Взять в ближайший набор — без бэкапа ретеншн опасен
> - Выкинуть
> 3. `guessit-sputnik` — вынести распознавание в сервис-спутник
> - Понизить до сырья (`--type research`) *(рекомендую)* — не проходит тест «готова к взятию»
> - Оставить задачей
Каждый вариант несёт причину — ту самую, что уедет в `--reason`. Ответы применяй
сразу и, если в порции осталось ещё, следующей итерацией показывай следующие ≤3.
## Шаг 4. Выбор цели и набор спринта
1. **Покажи состояние проекта**: секцию `Готово` (что приложение уже умеет —
это половина ответа на «где мы»), затем `Запланировано` с обоснованием
очереди, `Направления`, и
по каждой цели-кандидату — сколько под ней задач без открытых вопросов
(`list --goal <слаг>`). Цель без готовых задач набором не станет: её сперва
надо декомпозировать.
2. **Цель называет человек** — либо называет, что цели не будет. Это
продуктовое решение, а не механика: агент предлагает и объясняет, но не
выбирает. **Оба ответа законны**, и «без цели» — такой же ответ, как слаг:
спринт бывает под багфикс, под техдолг, под здоровье проекта. Спрашивается он
так же, как цель, и в отдельный вопрос не выносится: это один и тот же вопрос
«подо что набираем».
3. **Набор собирает агент** — `sprint start --goal <слаг>` (или `sprint start
--no-goal`), затем `sprint take …`. Скрипт не даст взять цель, задачу с чужой
целью, с открытым вопросом, без типа и **без разделов, которых требует её
тип** (у `fix` это в том числе `Воспроизведение`, у `research` — `Вопрос` и
`Куда ляжет ответ`, и сырьё поэтому не берётся вовсе). Задача без цели (`fix`,
`chore`, `research`) берётся свободно — операционная работа входит в набор
помимо его цели. **В спринте без цели чужой цели нет вовсе**: сверять не с
чем, берётся что угодно готовое, и единственной защитой остаётся показ набора
человеку.
4. **Набор показывается человеку до старта работ.** Показ — это и есть момент
заморозки: после него набор не двигается. **В показе называется состав по
типам** — три `fix` и ни одной `feature` под целью развития это разговор про
цель, а не про набор, и увидеть его надо до заморозки, а не в докладе по
итогам. **У набора без цели показ — единственная проверка состава**: скрипту
там отказывать не по чему, и «что угодно готовое» превращается в осмысленный
набор только глазами человека.
Здесь же последний дешёвый момент заметить **разнородную задачу**: раздел
«Затрагивает» показывает границы до того, как заведено предложение об
изменении. Строка, которая одна тянет задачу на ступень выше остального
перечня, — кандидат на разрез (шов — в `tasks`, `references/split.md`).
Замеченная здесь, она стоит одного `edit`; замеченная на ревью — выброшенного
предложения.
5. Задача, которой для взятия не хватает только разделов её типа, дописывается
здесь же — критерии с оракулами, перечень границ, шаги воспроизведения. Но
если для этого нужен ответ человека, это вопрос, и задача в набор не идёт.
**Размер — ориентир, а не закон:** 5–8 задач. Можно взять больше, можно меньше —
набор под цель важнее круглого числа; одна крупная задача спринтом тоже бывает.
## Доклад сессии
- Что просмотрено: N из M, сколько порций, по какому признаку отобраны.
- Вопросы: разобрано N, из них отвечено без человека N, снято тегов N.
- Разбор процесса: что записано и куда.
- Сверка документов с кодом: звался ли `doc-code-drift`, что проверено из
названного, что разошлось.
- Изменения списком: удалено как реализованное (со ссылками), ушло без
реализации (с причинами), понижено до сырья, слито, сменило тип или цель.
- Новый спринт: цель — **или строка «без цели» с объяснением, почему** (багфикс,
техдолг, здоровье), — набор со слагами, дата, состав по типам.
- **Границы покрытия**: сколько задач не трогали и какие именно секции, теги или
цели остались — иначе доклад читается как «беклог разобран».
- `tasks.py check` после правок — результат строкой.