Files
dev-skills/av-dev-pm/skills/session/SKILL.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

304 lines
26 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.
---
name: session
description: "Ритуал между спринтами и ведение самого спринта: разбор накопившихся вопросов, разбор прошедшего спринта про процесс, переоценка задач порциями, выбор цели (или решение, что спринт без цели) и набор нового спринта с заморозкой. Плюс правила по ходу спринта — что врывается в замороженный набор, чем вопрос отличается от блокера, когда задача выходит из спринта, что считается сделанным и что идёт в доклад. Использовать, когда просят закрыть или начать спринт, собрать набор, разобрать вопросы, провести груминг/переоценку/ретроспективу, решить «что делать дальше» или доложить итоги, а также когда вернулись к проекту после перерыва и надо понять, где остановились. Формат и содержимое задач — скилл tasks."
---
# Сессия между спринтами
Работа идёт спринтами: **набор задач, замороженный до конца спринта** — обычно
под одну цель, но бывает и без неё. Между спринтами — одна сессия из четырёх шагов. Этот скилл владеет
**ритуалом**: как сессия проводится и как спринт ведётся. Форматом и содержимым
задач владеет скилл `tasks`, выполнением задачи — пайплайн проекта.
## Почему не Scrum
Терминология близка — спринт, груминг, определение готовности, ретроспектива, —
и это удобно: не нужно изобретать слова. Но добрая половина Scrum существует
ради синхронизации людей, которых здесь нет.
**Не берём:** тайм-бокс (спринт ограничен объёмом, а не временем), velocity и
оценки в очках, ежедневный стендап (стендап — это и есть диалог), планирование
отдельно от груминга (владелец беклога один), роль скрам-мастера.
**Берём:** цель спринта, заморозку набора, определение готовности, груминг —
каждое потому, что снимает решение, которое иначе принимается заново каждый раз.
**Ретроспективу берём содержанием, но не отдельным ритуалом:** она шаг той же
сессии. Процесс личный, синхронизировать некого, а отдельная встреча ради трёх
вопросов — та самая плата ритуалом без выгоды.
## Роли
**Человек** выбирает цель спринта — **или решает, что этот спринт без цели**, —
разбирает вопросы, держит право на необратимое и на истину в самих данных.
**Агент — оркестрация.** Он собирает набор под названную цель, ставит задачи,
принимает отчёты и докладывает. Кто именно делает задачу — исполнитель, сабагент,
пайплайн — дело проекта; сессия про это не знает и знать не должна.
## Единицы
- **Цель** — то, ради чего набирается спринт. Файл типа `goal` (🎯),
перечисленный в `ROADMAP.md`. Цель постоянна: живёт, пока живёт направление, —
и уходит вместе с ним, если замысел оказался неверен (порядок отмены — в
[tasks](../tasks/references/task-goal.md#отменённая-цель--сперва-задачи-потом-цель)).
- **Задача** — то, что мерджится целиком и даёт видимую пользу.
- **Вопрос** — решение человека. Не останавливает начатую работу, но **блокирует
взятие** задачи в спринт. Живёт внутри файла задачи разделом «Вопросы» и тегом
`question`.
- **Блокер** — состояние, когда спринт не может продолжаться **ни одной**
задачей.
- **Спринт** — набор задач, замороженный до его конца. Под одной целью — или
**без цели вовсе**, законно: багфикс, техдолг, спринт здоровья. Такой набор
собран по работоспособности, а не по направлению, и заводится явно
(`sprint start --no-goal`).
## Вопрос, блокер, необратимое
| | Что это | Когда спрашиваем | Что останавливает |
| --- | --- | --- | --- |
| **Вопрос** | решение человека | на сессии, пачкой | взятие задачи в спринт |
| **Блокер** | спринт не может продолжаться ни одной задачей | немедленно | всё |
Право на **необратимое** — третье и отдельное: что именно необратимо, называет
`CLAUDE.md` проекта, и спрашивается оно всегда, независимо от спринта.
**Блокер определяется исходом, а не одновременностью.** Встали разом или
высыпались из спринта по одной — если продолжать нечем, это блокер: спринт
распускается (`sprint close --dissolve --reason …`), человек спрашивается
немедленно. Иначе спринт, из которого задачи вышли поштучно, выглядел бы штатно
завершённым, а вопросы тихо ждали бы сессии.
**Отличать вопрос от застревания.** Правило про остаток принадлежит управлению
задачами: оно решает, **сделана задача или вышла**, а это исход планирования, не
исполнения. **Ниже канонический текст; пайплайн проекта на него ссылается, а не
пересказывает** — два экземпляра одного правила разъезжаются, и разъезжаются
незаметно, потому что расхождение видно только на редком входе.
> Есть остаток, который доводится без ответа, — задача продолжается, вопрос
> записывается в файл. Остатка нет — задача выходит из спринта.
С двумя оговорками, без которых тест ошибается:
> **Остаток, который материализует нерешённое** — записывает в хранилище,
> журнал, витрину **или наружу** состояние, зависящее от неотвеченного вопроса,
> — **не остаток**. Решение поднимается до начала записи: откатить запись
> дороже, чем подождать ответ, а иногда невозможно. «Наружу» — часть правила, а
> не пример: выкладка, публикация и отправка данных третьей стороне не
> откатываются тем более.
> **Пол для остатка:** остаток, из которого пропала польза, названная в «зачем», —
> это не сделанная задача, а вышедшая из спринта.
## Заморозка набора
**Целей не больше одной.** Названа цель — набор служит ей: задача под чужой
целью в спринт не попадает, даже если взять удобно (`sprint take` это и
запрещает). **Задача с открытым вопросом в набор не берётся** — это верно всегда.
**Спринт без цели — законный случай, а не недосмотр.** Багфикс, техдолг,
здоровье: работа на работоспособность, а не на направление. Цель не названа —
сверять нечего, и в такой набор идёт что угодно готовое к взятию, в том числе
задачи под разными целями. Заводится он **явно**, `sprint start --no-goal`:
забытый флаг и решение человека иначе неотличимы, а это решение продуктовое.
Взамен проверки цели остаётся доклад — спринт без цели **называется таковым и
объясняется** одной строкой.
**Новая работа падает в беклог, а не в идущий спринт.** Решение «врываться или
отложить» принимается один раз правилом, а не заново каждый раз. Врывается
только два класса:
1. **Необратимый ущерб** — потеря, порча или утечка данных: то, что не чинится
доделкой потом.
2. **Сломан общий станок** — красная проверка, на которой стоит определение
готовности **всех** задач набора. Это не новая работа, а починка того, на чём
делается вся остальная.
Что в проекте считается необратимым ущербом и что — общим станком, называет
`CLAUDE.md` проекта. Не названо — спрашиваем человека, а не решаем сами.
**Конец спринта** — когда каждая задача набора либо сделана, либо вышла с
записанной причиной. Не «все сделаны»: иначе одна застрявшая задача держит
спринт бесконечно. Пустой набор закрывается `sprint close` — скрипт не даст
закрыть непустой.
Ведение спринта целиком — исходы задачи, определение готовности, приёмка,
доклад — [references/sprint.md](references/sprint.md).
## Сессия: четыре шага в этом порядке
Это зависимость, а не список.
1. **Разбор вопросов.**
2. **Разбор прошедшего спринта — про процесс, а не про задачи.** Здесь же оба
судьи документов канона на весь канон разом, раз в спринт: `doc-consistency`
(документы между собой) и `doc-code-drift` (документы против кода).
3. **Переоценка задач** порциями.
4. **Выбор цели и набор спринта.** Цель называет человек — либо называет, что
этот спринт без цели; набор собирает агент и показывает **до старта работ**.
Рёбра подписаны тем, что ломается при их нарушении:
```mermaid
flowchart TD
check["tasks.py check (+ --fix)<br/>результат — строкой в доклад"]
s1["1. Разбор вопросов<br/>пачкой, не больше трёх за раз"]
s2["2. Разбор прошедшего спринта<br/>про процесс → docs/review.md"]
s3["3. Переоценка задач порциями"]
s4["4. Цель называет человек,<br/>набор собирает агент"]
sprint["спринт: набор заморожен"]
check --> s1
s1 --> s2
s2 --> s3
s1 -->|"неотвеченный вопрос → переоценка вслепую"| s3
s3 -->|"без переоценки набор берётся из протухшего"| s4
s4 --> sprint
```
Схема — **сводка**: процедура каждого шага в
[references/cadence.md](references/cadence.md), и при расхождении прав текст.
## Вернулся, а спринт открыт
Сессия — ритуал **между** спринтами, и шаг 1 предполагает только что закрытый.
Вход после перерыва другой, и начинается он не с шага, а с вопроса, свой ли ещё
набор:
1. `tasks.py check` — блок здоровья скажет состояние спринта, число готовых к
взятию и залежавшихся; при расхождении раскладки `--fix`.
2. Прочитать `SPRINT.md`: цель (или запись, что её нет), состав, дата начала.
3. **Развилка, и решает её человек.** Набор всё ещё твой — продолжай спринт, ни
сессии, ни переоценки не нужно, они между спринтами. Взялся перечитывать,
зачем эти задачи собраны вместе, — набор протух:
`sprint close --dissolve --reason …`, недоделанное возвращается в беклог,
дальше обычная сессия с шага 1.
Порога в неделях нет намеренно — почему, в
[references/sprint.md](references/sprint.md), «Протухший набор».
Середины у развилки тоже нет: «доделаю пару штук и решу» — это работа по набору,
которого ты уже не понимаешь.
Процедура каждого шага, размер и отбор порции, храповик на залежавшихся, формат
интерактива и доклад — [references/cadence.md](references/cadence.md).
## Инструмент
Тот же `tasks.py`, что у скилла `tasks` — оба скилла в одном плагине, путь
общий: `tk="$CLAUDE_PLUGIN_ROOT/skills/tasks/scripts/tasks.py"`. Сессии нужны
прежде всего:
```
python3 $tk check --dir D # с этого начинается любая сессия
python3 $tk list --dir D --questions # шаг 1: что накопилось
python3 $tk list --dir D --tag sprint:<слаг> # шаг 3: урожай спринта, первая порция
python3 $tk list --dir D --stale # шаг 3: дальше по залежалости
python3 $tk list --dir D --goal <слаг> # шаг 4: кандидаты под названную цель
python3 $tk sprint start --dir D --goal <слаг> # шаг 4: заводит и слаг спринта
python3 $tk sprint start --dir D --no-goal # шаг 4: набор без цели, явным флагом
python3 $tk sprint take --dir D <слаг> … # шаг 4: набор
python3 $tk sprint close --dir D # конец спринта; --dissolve при блокере
python3 $tk reopen <слаг> --dir D --reason … # приёмка не сошлась после закрытия
```
`D` — каталог задач проекта, по канону всегда `docs/tasks`; `--dir` передаётся
явно каждой командой. Вызов из чужого контекста описан в скилле `tasks`
(«Переносимость»). **Коды выхода** — там же: 1 это дрейф в беклоге, 3 это
«каталога нет», и ветвиться на них надо по-разному.
**Слаг спринта заводит `sprint start`** (по умолчанию — дата) и пишет его в
`SPRINT.md`; всё заведённое **при открытом спринте** помечается `sprint:<слаг>`
автоматически. Поэтому «первая порция — урожай прошедшего спринта» работает без
чьей-либо памяти — но ровно до команды `sprint close`, которая `SPRINT.md`
очищает. Отсюда порядок: **урожай заводится до закрытия, слаг для сессии берётся
из отчёта `sprint close`** ([references/sprint.md](references/sprint.md)).
Правки задач делаются мутациями (`edit`, `move`, `close`), а не редактором:
руками правится только тело файла. Это правило скилла `tasks`, здесь оно не
пересказывается.
## Стимулы, которые процесс создаёт
Правило, которое можно обойти в свою пользу, будет обойдено.
**Приёмщик и исполнитель здесь совпадают, и это надо назвать вслух.** Задачу
закрывает и двигает по индексам агент-оркестратор — тот же, кто её и сделал.
Прежде границу держала механика: моста между плагинами не было, и закрыть задачу
пайплайн физически не мог. Теперь мост есть, и защита у трёх обходов ниже —
**только текстовая**. Опоры, которые остались настоящими:
- **независимый отчёт ревью** — артефакт, написанный не исполнителем; по нему
сверяют состав прогона и урожай. Где он лежит, знает пайплайн проекта; при
конвейере `av-dev-pipeline` это отчёт триажа в
`openspec/changes/archive/<id>/review/` (до архивации — `changes/<id>/review/`);
- **`SPRINT.md` под git** — `git log -p` показывает, что и когда было закрыто.
Работает, только если закрытие **закоммичено**: удаление файла задачи и правка
индекса, оставшиеся в рабочем дереве, никакой истории не образуют;
- **`reopen <слаг> --reason`** — закрытие не окончательно. Приёмка человеком на
сессии его отменяет, и это штатная операция, а не скандал.
Известные обходы:
- **Скрыть блокер** — он останавливает всё и выглядит как провал исполнителя.
Защита: тест про остаток плюс прямая запись, что **объявление блокера
неудачей не считается**.
- **Не записать вопрос** на задаче-кандидате, чтобы не вычеркнуть её из
ближайшего набора. Защита: вопросы кандидатов разбираются на той же сессии
**вне очереди порции**.
- **Занизить критерии приёмки**, раз они пол. Защита ослаблена: правит их тот же,
кто по ним отчитывается. Остаётся требование, что расхождение критериев с
сутью — **дефект критериев, о котором сообщают, а не молча дорабатывают**, и
переоценка на сессии, где критерии видит человек.
- **Сжать задачу до остатка** и отчитаться «сделана». Защита ослаблена там же.
Пол для остатка — польза, названная в «зачем»; проверяет его человек при приёмке,
и `reopen` — его инструмент.
- **Объявить спринт без цели**, чтобы не задавать человеку продуктовый вопрос:
набор без цели берёт что угодно, и собрать его можно молча. Защита: цели нет
— это **ответ человека, а не умолчание** (`--no-goal` спрашивается так же, как
цель), плюс строка доклада, называющая спринт бесцельным и объясняющая почему.
Два бесцельных спринта подряд — предмет разбора процесса, а не мелочь.
- **Занизить урожай** — не заводить найденное по ходу. Защита: поимённая сверка
с **сохранённым независимым отчётом**, а не с прозой исполнителя. Каждая
отложенная находка имеет либо слаг, либо строку «не заведена: причина».
Нулевой урожай при непустом отчёте виден сразу.
**Проект без конвейера ревью — независимого отчёта нет, и это надо сказать, а не
обойти молча.** Задачи делались руками или чужим пайплайном, сверять урожай не с
чем: остаётся проза исполнителя, то есть тот же взгляд, что и у автора. Тогда
защита от занижения урожая **снята**, и доклад спринта обязан нести строку «урожай
сверялся с отчётом исполнителя — независимого отчёта в проекте нет». Дальше это
решение человека: завести конвейер, принимать выборочной перепроверкой или
согласиться с ценой. Молчание здесь хуже любого из трёх исходов.
Стимулы внутри пайплайна задачи (занизить требования к проверке, пропустить
проход) принадлежат ему и защищены там же.
## Слоты проекта
Сессия не знает ни языка, ни сборки, ни CI. На часть проектного отвечает своей
структурой [канон](../canon/references/canon.md): разбор процесса (шаг 2) живёт
в `docs/review.md`, оракулы и «чем краснеет безусловно» — в семантике гейта в
`CLAUDE.md`. Остальное проект **дописывает в `CLAUDE.md`**:
1. **Пайплайн задачи** — чем задача выполняется и что входит в его определение
готовности. Сессия требует только форму: пайплайн пройден + критерии приёмки
проверены поимённо.
2. **Общий станок** — какая проверка, покраснев, врывается в замороженный
спринт.
3. **Необратимое** — что спрашивается у человека всегда (тот же слот, что у
скилла `tasks`; дом один).
4. **Ориентир по размеру спринта**, если он замерялся. Умолчание — 5–8 задач, и
это **ориентир, а не закон**.
Слота «куда копируются критерии приёмки» здесь нет намеренно: на него отвечает
**пайплайн проекта** — он переносит критерии в описание изменения, когда его
заводит. Проект без пайплайна называет своё место сам, в слоте 1.
Числа проекта (сколько задач в спринте, сколько времени на задачу, каков прирост
беклога) — предмет шага 2, а не константы этого скилла.
## Чего этот скилл не делает
Не пишет код и не выполняет задачи. Не заводит и не переоформляет задачи сам по
себе — формат и содержимое ведёт `tasks` (сессия зовёт его операции). Не решает
за человека, какая цель следующая. Не двигает набор идущего спринта.