Цель была обязательной во всех трёх местах сразу: 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>
278 lines
25 KiB
Markdown
278 lines
25 KiB
Markdown
# Сессия: четыре шага
|
||
|
||
Одна сессия между спринтами. Порядок шагов — **зависимость, а не список**:
|
||
переоценивать задачи, не разобрав вопросы, значит переоценивать вслепую; набирать
|
||
спринт, не переоценив, значит набирать из протухшего.
|
||
|
||
Начинается сессия с `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` после правок — результат строкой.
|