спринт без цели: цель стала необязательной, но не молчаливой

Цель была обязательной во всех трёх местах сразу: 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>
This commit is contained in:
av
2026-08-07 09:19:29 +03:00
co-authored by Claude Opus 5
parent 4386eb3e1c
commit 37394444e8
7 changed files with 217 additions and 69 deletions
+20 -10
View File
@@ -225,19 +225,28 @@
по каждой цели-кандидату — сколько под ней задач без открытых вопросов
(`list --goal <слаг>`). Цель без готовых задач набором не станет: её сперва
надо декомпозировать.
2. **Цель называет человек.** Это продуктовое решение, а не механика: агент
предлагает и объясняет, но не выбирает.
3. **Набор собирает агент** — `sprint start --goal <слаг>`, затем `sprint take
`. Скрипт не даст взять цель, задачу с чужой целью, с открытым вопросом, без
типа и **без разделов, которых требует её тип** (у `fix` это в том числе
`Воспроизведение`, у `research` — `Вопрос` и `Куда ляжет ответ`, и сырьё
поэтому не берётся вовсе). Задача без цели (`fix`, `chore`, `research`)
берётся свободно — операционная работа входит в набор помимо его цели.
2. **Цель называет человек** — либо называет, что цели не будет. Это
продуктовое решение, а не механика: агент предлагает и объясняет, но не
выбирает. **Оба ответа законны**, и «без цели» — такой же ответ, как слаг:
спринт бывает под багфикс, под техдолг, под здоровье проекта. Спрашивается он
так же, как цель, и в отдельный вопрос не выносится: это один и тот же вопрос
«подо что набираем».
3. **Набор собирает агент** — `sprint start --goal <слаг>` (или `sprint start
--no-goal`), затем `sprint take …`. Скрипт не даст взять цель, задачу с чужой
целью, с открытым вопросом, без типа и **без разделов, которых требует её
тип** (у `fix` это в том числе `Воспроизведение`, у `research` — `Вопрос` и
`Куда ляжет ответ`, и сырьё поэтому не берётся вовсе). Задача без цели (`fix`,
`chore`, `research`) берётся свободно — операционная работа входит в набор
помимо его цели. **В спринте без цели чужой цели нет вовсе**: сверять не с
чем, берётся что угодно готовое, и единственной защитой остаётся показ набора
человеку.
4. **Набор показывается человеку до старта работ.** Показ — это и есть момент
заморозки: после него набор не двигается. **В показе называется состав по
типам** — три `fix` и ни одной `feature` под целью развития это разговор про
цель, а не про набор, и увидеть его надо до заморозки, а не в докладе по
итогам.
итогам. **У набора без цели показ — единственная проверка состава**: скрипту
там отказывать не по чему, и «что угодно готовое» превращается в осмысленный
набор только глазами человека.
Здесь же последний дешёвый момент заметить **разнородную задачу**: раздел
«Затрагивает» показывает границы до того, как заведено предложение об
@@ -261,7 +270,8 @@
названного, что разошлось.
- Изменения списком: удалено как реализованное (со ссылками), ушло без
реализации (с причинами), понижено до сырья, слито, сменило тип или цель.
- Новый спринт: цель, набор со слагами, дата, состав по типам.
- Новый спринт: цель — **или строка «без цели» с объяснением, почему** (багфикс,
техдолг, здоровье), — набор со слагами, дата, состав по типам.
- **Границы покрытия**: сколько задач не трогали и какие именно секции, теги или
цели остались — иначе доклад читается как «беклог разобран».
- `tasks.py check` после правок — результат строкой.
+11 -8
View File
@@ -1,7 +1,8 @@
# Ведение спринта
Спринт — набор задач под одну цель, замороженный до его конца. Здесь то, что
происходит **внутри** спринта: как задача заканчивается, что считается сделанным,
Спринт — набор задач, замороженный до его конца: под одну цель или **без цели**
(багфикс, техдолг, здоровье — это законно, `sprint start --no-goal`). Здесь то,
что происходит **внутри** спринта: как задача заканчивается, что считается сделанным,
кто принимает и что идёт в доклад. Как спринт набирается — шаг 4 в
[cadence.md](cadence.md).
@@ -21,7 +22,7 @@
- **Оказалась крупнее задачи** — распознаётся **до того, как под неё заведено
предложение об изменении**, иначе его придётся выбрасывать. Выходит из набора,
уходит на декомпозицию; спринт продолжается остальными, части заводятся под той
же целью и в замороженный набор не добавляются.
же целью (у спринта без цели — без неё) и в замороженный набор не добавляются.
- **Отменена решением по ходу** — `close <slug> --reason "<ссылка на решение>"`
прямо из спринта. Это редкий, но законный исход, и он называется в докладе.
@@ -170,9 +171,9 @@ flowchart TD
Только два класса — правило и его обоснование в SKILL.md. Здесь механика:
- вторжение **не добавляет** задачу в набор: `SPRINT.md` остаётся набором под
цель. Внеплановая работа делается и называется в докладе отдельной строкой
«внеплановое: что и почему»;
- вторжение **не добавляет** задачу в набор: `SPRINT.md` остаётся тем набором,
который заморозили и показали. Внеплановая работа делается и называется в
докладе отдельной строкой «внеплановое: что и почему»;
- если внеплановое требует больше пары часов, честнее распустить спринт, чем
делать вид, что набор соблюдается;
- всё остальное падает в беклог через обычный интейк и ждёт сессии.
@@ -181,8 +182,10 @@ flowchart TD
Проверяемые якоря, а не пересказ:
- **Цель спринта** и по каждой задаче набора: **хеш коммита**, дословный исход
проверок проекта, **исход по каждому критерию приёмки**.
- **Цель спринта** — или строка «спринт без цели» с тем, чем он был (багфикс,
техдолг, здоровье): у бесцельного набора это единственное место, где состав
вообще объясняется. И по каждой задаче набора: **хеш коммита**, дословный
исход проверок проекта, **исход по каждому критерию приёмки**.
- **Какие развилки решались** и чем обоснованы.
- **Урожай:** сколько задач заведено, какие вопросы накопились, что вышло из
спринта и почему, что было внеплановым.