Основной инструмент владельца отвечал на половину своего вопроса. Оценка идёт по поведению: что приложение уже может и чего ещё не может, — а close --implemented удалял у достигнутой цели и файл, и строку, так что роадмап по построению показывал только «что осталось». Свидетельство лежало в самом роадмапе healthlog: секция «Что уже пройдено» на двадцать строк прозы, руками, с припиской «Эти звенья целями не заведены: закрытая цель записи не оставляет». Теперь строка с датой переезжает в секцию достигнутого, файл удаляется по-прежнему. Вторым домом поведения это не делает: нормативное поведение живёт в openspec/specs, роадмап отвечает, когда и в каком порядке оно появилось. Ссылки на файл в строке нет — файла больше нет, форма как в REJECTED.md. Цель стала возможностью приложения, задача — шагом к ней: - заголовок цели отвечает на «что приложение будет уметь»; свойство поведения («сообщает о своём состоянии», «исход не зависит от порядка») — тоже возможность и переформулировки не требует; - «Завершение» — списком, а не абзацем: задача ссылается на его строку, и это новая защита от «отрефакторить X» вместо прежнего «наблюдаемо снаружи». Заодно видно обратное: строка, к которой не относится ни одна задача, — незакрытая часть возможности; - работа над инструментом и процессом на этот вопрос не отвечает и живёт в отдельной секции. Цель обязательна не у всякой задачи. Прежнее «иначе она не попадёт ни в один спринт» было угрозой, а не аргументом, и заставляло операционную работу выдумывать себе направление. Граница по роду: feature без цели не бывает, fix, chore и research живут без неё и входят в набор помимо цели спринта. Тип [epic] упразднён: зонтиком стала цель, а слишком крупный шаг дробится под ней. Ноль употреблений на 97 записей двух живых проектов. Секции роадмапа — умеет / строим / направления / станок, четыре вместо двух; имена приняты как временные и запаркованы (TODO 7). Имя секции достигнутого знает скрипт — docs/.pm.json, ключ tasks.achieved_section. reopen цели снимает строку достигнутого, круг проверен вживую. Всё дописано в версию 3 канона: она ещё нигде не выкачена. DECISIONS 19, YYY–ГГГ и следствия 78–81. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
187 lines
15 KiB
Markdown
187 lines
15 KiB
Markdown
# Ведение спринта
|
||
|
||
Спринт — набор задач под одну цель, замороженный до его конца. Здесь то, что
|
||
происходит **внутри** спринта: как задача заканчивается, что считается сделанным,
|
||
кто принимает и что идёт в доклад. Как спринт набирается — шаг 4 в
|
||
[cadence.md](cadence.md).
|
||
|
||
## Наблюдаемые исходы задачи
|
||
|
||
Как они достигаются — дело пайплайна проекта. Сессия знает только исход и его
|
||
след.
|
||
|
||
- **Сделана** — по определению готовности ниже. `close <slug> --implemented`:
|
||
файл и строка удаляются, следом остаётся коммит. **Закрывает агент-оркестратор
|
||
последним шагом пайплайна, после коммита; приёмка человеком идёт позже и
|
||
отменяется `reopen`** — см. «Кто и когда закрывает».
|
||
- **Вышла из спринта** — `sprint drop <slug> --reason …`: возвращается в беклог
|
||
с вопросом в файле и **без живого незакоммиченного предложения** — иначе при
|
||
следующем взятии оно столкнётся с новым. Наработки, которые жалко терять,
|
||
переезжают в тело задачи текстом.
|
||
- **Оказалась крупнее задачи** — распознаётся **до того, как под неё заведено
|
||
предложение об изменении**, иначе его придётся выбрасывать. Выходит из набора,
|
||
уходит на декомпозицию; спринт продолжается остальными, части заводятся под той
|
||
же целью и в замороженный набор не добавляются.
|
||
- **Отменена решением по ходу** — `close <slug> --reason "<ссылка на решение>"`
|
||
прямо из спринта. Это редкий, но законный исход, и он называется в докладе.
|
||
|
||
**Конец спринта** — когда по каждой задаче набора наступил один из исходов. Не
|
||
«все сделаны»: иначе одна застрявшая задача держит спринт бесконечно. Затем
|
||
`sprint close`.
|
||
|
||
```mermaid
|
||
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 …`), недоделанное возвращается в беклог, новый набор
|
||
делается после ответа человека. Спринт не «ждёт»: ждать может человек, а
|
||
замороженный набор, который нельзя двигать, только мешает.
|
||
|
||
## Определение готовности
|
||
|
||
Задача засчитывается сделанной, когда верно **всё**:
|
||
|
||
1. **Пайплайн задачи пройден до конца** — со своим определением готовности, за
|
||
которое отвечает проект: проверки, состав ревью, документация, коммит. Здесь
|
||
оно не пересказывается и не подменяется — **форма фиксирована, содержание
|
||
даёт `CLAUDE.md` проекта**. Пайплайна нет, задача сделана руками — условие
|
||
читается как «проверки проекта зелёные и изменение влито».
|
||
2. **Критерии приёмки проверены поимённо** — каждый со своим оракулом, исход по
|
||
каждому назван. Это единственное, что добавляет управление задачами: пайплайн
|
||
отвечает «сделано по правилам», критерии — «сделано то, что заказывали».
|
||
3. **Находки по ходу отданы списком** — исполнитель обязан их **назвать**
|
||
(каждую, с пометкой «заведена / не заведена: причина»), но **не обязан
|
||
заводить**: заведение интерактивно, оно требует дедупликации против беклога и
|
||
кладбища и решений человека. Обязанность **завести урожай** — на закрытии
|
||
спринта, ниже. Так автономный исполнитель не оказывается одновременно обязан
|
||
завести задачи и не вправе это сделать в одиночку.
|
||
|
||
### Кто и когда закрывает
|
||
|
||
**Задачу закрывает агент-оркестратор — тот же, кто её и сделал**, последним шагом
|
||
пайплайна, после коммита. Порядок:
|
||
|
||
1. пайплайн доводит задачу до коммита;
|
||
2. **после коммита** зовёт `Skill av-dev-pm:tasks` и закрывает задачу
|
||
(`close <slug> --implemented`); строка уходит из `SPRINT.md`;
|
||
3. **докладывает исход и по каждому критерию — оракул и наблюдаемый исход.**
|
||
Это доклад приёмщику, а не отметка «принято».
|
||
|
||
**Приёмщик и исполнитель здесь совпадают, и это принято сознательно** — цена
|
||
названа в `SKILL.md`, раздел «Стимулы». Поэтому закрытие **не окончательно**, а
|
||
доклад по критериям — не формальность: он единственное, по чему приёмка вообще
|
||
возможна.
|
||
|
||
**Порядок «коммит, потом закрытие» обязателен.** Закрытие удаляет файл задачи;
|
||
упавший коммит после закрытия оставил бы задачу закрытой без единого следа
|
||
работы.
|
||
|
||
**Само закрытие тоже коммитится, отдельным коммитом.** Удаление файла задачи и
|
||
правка индекса — правки в рабочем дереве; пока они не в истории, `SPRINT.md`
|
||
ничего не показывает, а `reopen` восстанавливает текст из `HEAD` в узком окне,
|
||
которое закончится первым посторонним коммитом. Сообщение про учёт, а не про
|
||
работу: `закрыта задача <slug>`.
|
||
|
||
**Дорога назад существует и обязана быть названа.** Человек на сессии сверил
|
||
критерии, и приёмка не сошлась — `tasks.py reopen <slug> --reason "приёмка не
|
||
сошлась: …"`:
|
||
файл восстанавливается из истории git, строка возвращается в набор идущего
|
||
спринта (или в беклог, если спринта нет), строка кладбища снимается. Тело
|
||
восстанавливается **на момент удаления** — всё, что было дописано позже, живёт
|
||
только в коммите задачи, и это называется в докладе.
|
||
|
||
### Кто и по чему принимает
|
||
|
||
Три условия, без которых пункт про критерии не исполняется никем:
|
||
|
||
1. **Критерии переживают файл задачи.** Файл удаляется при закрытии, поэтому
|
||
критерии копируются туда, где их увидит приёмщик. Куда именно — **отвечает
|
||
пайплайн проекта, а не слот в `CLAUDE.md`**: он переносит их в `tasks.md`
|
||
изменения на шаге заведения change. Проект без пайплайна называет своё место
|
||
сам.
|
||
2. **Принимает человек на сессии, а не отдельный агент.** Декорреляция
|
||
исполнителя и приёмщика в момент закрытия **снята** (решение о снятии и его
|
||
цена — в `SKILL.md`, «Стимулы»). Опоры остались три: **сохранённый независимый
|
||
отчёт ревью** (при конвейере `av-dev-pipeline` — отчёт триажа в
|
||
`openspec/changes/archive/<id>/review/`, до архивации — `changes/<id>/review/`),
|
||
`SPRINT.md` под git и `reopen`. Переоценка на сессии и есть момент, когда
|
||
критерии видит не исполнитель. **Конвейера ревью в проекте нет — первой опоры
|
||
нет тоже**, и это называется строкой доклада, а не обходится молча
|
||
(`SKILL.md`, «Стимулы»).
|
||
3. **Расхождение — дефект критериев.** Приёмщик правит критерии и возвращает
|
||
задачу исполнителю **в этом же спринте**: ответ есть, остаток есть, по тесту
|
||
про остаток это не выход из спринта.
|
||
|
||
## Что врывается в замороженный набор
|
||
|
||
Только два класса — правило и его обоснование в SKILL.md. Здесь механика:
|
||
|
||
- вторжение **не добавляет** задачу в набор: `SPRINT.md` остаётся набором под
|
||
цель. Внеплановая работа делается и называется в докладе отдельной строкой
|
||
«внеплановое: что и почему»;
|
||
- если внеплановое требует больше пары часов, честнее распустить спринт, чем
|
||
делать вид, что набор соблюдается;
|
||
- всё остальное падает в беклог через обычный интейк и ждёт сессии.
|
||
|
||
## Доклад в конце спринта
|
||
|
||
Проверяемые якоря, а не пересказ:
|
||
|
||
- **Цель спринта** и по каждой задаче набора: **хеш коммита**, дословный исход
|
||
проверок проекта, **исход по каждому критерию приёмки**.
|
||
- **Какие развилки решались** и чем обоснованы.
|
||
- **Урожай:** сколько задач заведено, какие вопросы накопились, что вышло из
|
||
спринта и почему, что было внеплановым.
|
||
- **Поимённая сверка урожая** с независимыми отчётами ревью: каждая отложенная
|
||
находка имеет либо слаг, либо строку «не заведена: причина». Нулевой урожай при
|
||
непустом отчёте — сигнал, а не благополучие. **Отчётов нет** (проект без
|
||
конвейера ревью) — сверять не с чем, и строка доклада говорит именно это, а не
|
||
«сверено».
|
||
- **Созрела ли порция для сессии.** Решение звать — человека, напоминание —
|
||
обязанность агента: `⌈урожай / 8⌉` порций.
|
||
- **Границы покрытия** сжатой строкой: что в этом спринте не проверялось вовсе.
|