- атомарность: sprint drop и move собирают план правок целиком и пишут одним проходом; раньше отказ на втором слаге оставлял первый файл переписанным при нетронутом индексе - хук переехал в мета-строку файла: индекс стал производным, и check --fix больше не теряет текст, восстанавливая строку - механизировано то, что было записано, но не проверялось: слаг спринта и автотег, отказ по факту непустого раздела вопросов, число критериев, пометка decomposed, покрытие причин - reopen возвращает закрытую задачу: без него порядок «пайплайн доложил → приёмщик судит → владелец закрывает» был односторонним - скилл adopt: приходит в чужой репозиторий и выводит заполненный каталог задач. На копии беклога healthlog — 12 целей, 38 задач, 36 переименований, 86 ссылок в 36 файлах, check зелёный
344 lines
31 KiB
Markdown
344 lines
31 KiB
Markdown
---
|
||
name: tasks
|
||
description: Ведение задач и целей как каталога markdown-файлов (одна запись = один файл в items/ + строка в одном из индексов). Заведение задачи, идеи или цели из диалога, разбор находок аудита/ревью, декомпозиция на независимо полезные части, мозговой штурм идеи, гигиена полей и проверка согласованности индексов. Использовать, когда просят добавить задачу/идею/цель, превратить находки ревью в задачи, разбить задачу, проработать идею, поправить формат или проверить беклог. Ритуал между спринтами — скилл session. Не реализует задачи — этим занимается пайплайн проекта.
|
||
---
|
||
|
||
# Задачи
|
||
|
||
Задачи — каталог markdown-файлов. Одна запись = один файл `items/<slug>.md` плюс
|
||
строка **ровно в одном** индексе. Скилл владеет **форматом и содержимым**:
|
||
заводит, редактирует, закрывает, разбирает находки ревью, дробит, штурмует идеи.
|
||
|
||
Чем он **не** владеет: ритуалом между спринтами (разбор вопросов → разбор
|
||
прошедшего спринта → переоценка → выбор цели и набор) — это скилл `session`; и
|
||
выполнением задачи — это пайплайн проекта.
|
||
|
||
## Четыре правила, из которых всё следует
|
||
|
||
Ситуация не покрыта инструкцией — решай по ним.
|
||
|
||
1. **Беклог гниёт с той стороны, где его пополняют.** Заведение — самая частая
|
||
операция и с худшим отказом: из одного разговора рождается пять файлов, а
|
||
переоценка потом разгребает то, чего не надо было заводить. Дедупликация и
|
||
фильтр на входе дешевле любой чистки. Заводим только то, что **не делаем
|
||
сейчас** и о потере чего пожалеем.
|
||
2. **Файл — источник истины, индексы производны.** Разошлись — неправы индексы.
|
||
Согласованность механизируема и проверяется командой, а не вниманием: всё,
|
||
что ловит `tasks.py check`, не должно попадать ни в чек-лист, ни в промпт.
|
||
Поэтому **хук живёт в мета-строке файла**, а строка индекса его лишь
|
||
повторяет: пока хук лежал только в индексе, восстановление пропавшей строки
|
||
теряло его молча и навсегда. Единственное исключение намеренное: **в каком
|
||
индексе лежит задача, знают индексы** — «в спринте» это свойство спринта, а
|
||
не файла, поля-состояния нет.
|
||
3. **Причина переживает запись.** Выкинутая без причины задача вернётся через
|
||
квартал тем же текстом. Реализованная оставляет след в коммите — выкинутая не
|
||
оставляет ничего, поэтому у неё есть `REJECTED.md`.
|
||
4. **Порядка нет, есть цель.** Ни в секциях, ни списком: «что делать дальше»
|
||
отвечает набор спринта, а между спринтами порядок не нужен никому — брать
|
||
задачи вне спринта запрещает заморозка. Поэтому нет ни приоритетов, ни
|
||
«повысить», ни «встать раньше»: вместо повышения — смена цели или включение
|
||
в набор.
|
||
|
||
## Раскладка
|
||
|
||
```
|
||
<tasks>/ по умолчанию docs/tasks, путь настраивается
|
||
items/ задачи и цели файлами, <slug>.md, слаги английские
|
||
PLAN.md оглавление целей: линия (упорядоченная) и кусты
|
||
BACKLOG.md что можно взять — только задачи, целей здесь нет
|
||
SPRINT.md текущий спринт: цель, набор, дата
|
||
REJECTED.md ушедшее БЕЗ реализации, с причиной и датой
|
||
```
|
||
|
||
Правило, снимающее путаницу: **`BACKLOG.md` — то, что берут; `PLAN.md` — то,
|
||
подо что берут.** Цель в спринт взять нельзя, поэтому в списке берущихся ей не
|
||
место.
|
||
|
||
**Секции «блокеры» в беклоге нет.** Блокер — это *состояние* (спринт не может
|
||
продолжаться ни одной задачей), а не полка: он живёт ровно до ответа человека, и
|
||
записи в такой секции не успевают жить. Следы блокера остаются вопросами в
|
||
файлах задач распущенного спринта. Постоянно пустая секция со старой семантикой
|
||
«разбираются пачками» противоречила бы правилу «блокер эскалируется немедленно»,
|
||
поэтому `init` её заводить отказывается, а `check` о ней говорит. **Проекту,
|
||
который переезжает с такой секцией, её надо удалить** — это единственное место,
|
||
где это сказано.
|
||
|
||
**Задача живёт в одном индексе за раз.** Взята в спринт — строка переезжает из
|
||
`BACKLOG.md` в `SPRINT.md`; вышла — обратно. Файл в `items/` при этом **не
|
||
двигается**: он и есть запись, индексы лишь показывают, где она числится.
|
||
|
||
**У сделанной задачи записи не остаётся** — файл и строка удаляются (`close
|
||
--implemented`). Ей хватает коммита и документации проекта; вторая запись была
|
||
бы вторым домом для того же факта. Вопрос «что было в спринте N» отвечается
|
||
даром: `SPRINT.md` лежит под git, `git log -p <tasks>/SPRINT.md` отдаёт историю
|
||
всех наборов без отдельного журнала.
|
||
|
||
## Цели
|
||
|
||
**Цель — такой же файл в `items/`, тип `[goal]`**, перечисленный в `PLAN.md`:
|
||
либо звено упорядоченной **линии** продукта (с обоснованием порядка прозой),
|
||
либо тематический **куст** — цель, в последовательность не встающая («прочность
|
||
слияния», «журнал и пересборка»). Без второй части половина целей была бы нигде
|
||
не перечислена: находки ревью не служат ничему из линии.
|
||
|
||
- **Список задач цели выводится, а не хранится.** В теле цели — зачем она и что
|
||
считается её завершением; перечня задач там нет. Он был бы третьим индексом и
|
||
поехал бы на первой же закрытой задаче, а `check` про него не знает. Связь
|
||
однонаправленна: задача несёт тег `goal:<слаг>`, перечень даёт
|
||
`tasks.py list --goal <слаг>`.
|
||
- **Статус цели выводится.** Цель закрыта, когда у неё не осталось открытых
|
||
задач; `[x]`/`[~]` руками не ведутся, а `close` цели с живыми задачами
|
||
скрипт запретит. Единственная оговорка: цель без задач неотличима — «ещё не
|
||
разобрана» или «всё закрыто». Различает **тег `decomposed`** в мета-строке
|
||
цели: он ставится, когда цель разложена на задачи. Тег, а не строка в теле —
|
||
потому что проверяется механически: `check` требует его у пустой цели, а
|
||
`check --fix` сам проставляет его цели, у которой задачи есть.
|
||
- **`[goal]` и `[epic]` — разные вещи.** Цель **постоянна**: живёт, пока живёт
|
||
направление. Эпик **временен**: это задача, которая не мерджится целиком, её
|
||
разбирают, и он исчезает. Два срока жизни под одним словом разъезжаются,
|
||
поэтому слова два.
|
||
|
||
## Инструмент (`tasks.py`)
|
||
|
||
Пусть `tk="$CLAUDE_PLUGIN_ROOT/skills/tasks/scripts/tasks.py"`, а `D` — каталог
|
||
задач проекта (см. «Переносимость»; `--dir` опускается только если каталог
|
||
лежит в умолчаниях под текущим каталогом).
|
||
|
||
```
|
||
python3 $tk check --dir D # согласованность индексов + здоровье
|
||
python3 $tk check --dir D --fix # + починить дрейф (секция, заголовок, дубли, хук, дом)
|
||
python3 $tk list --dir D [--stale] [--section S] [--type T] [--tag a,b] [--goal S] [--index …] [--questions]
|
||
python3 $tk add --dir D --slug S --title T [--type goal|idea|epic] [--section S] [--goal G] [--hook H] [--tag a,b]
|
||
python3 $tk edit S --dir D [--title T] [--hook H] [--type T] [--goal G] [--add-tag a,b] [--rm-tag c]
|
||
python3 $tk move S --dir D --section S [--reason R] [--after S | --first]
|
||
python3 $tk close S --dir D --reason R # в REJECTED.md + удалить (ушла без реализации)
|
||
python3 $tk close S --dir D --implemented # просто удалить (реализована, приёмка сошлась)
|
||
python3 $tk reopen S --dir D --reason R # вернуть закрытую: приёмка не сошлась
|
||
python3 $tk sprint start --goal S --dir D | take S… | drop S… --reason R | close [--dissolve --reason R]
|
||
python3 $tk init --dir D [--sections …] [--plan-sections …] [--items …] [--backlog …] …
|
||
python3 $tk adopt scan --from … | apply --plan … # разовая адаптация чужого репозитория, скилл adopt
|
||
```
|
||
|
||
**Коды выхода — единый словарь; на нём ветвятся скиллы, а не на тексте вывода:**
|
||
|
||
| Код | Что случилось | Что делать |
|
||
| --- | --- | --- |
|
||
| 0 | сошлось / сделано | дальше по сценарию |
|
||
| 1 | **только `check`:** найден дрейф индексов и файлов | `check --fix`, остаток разобрать |
|
||
| 2 | ошибка употребления: аргументы или нарушенное правило | читать сообщение, это отказ по существу |
|
||
| 3 | окружение: каталог не найден, конфиг битый или мимо диска | чинится путём или `.tasks.json`, повтор не поможет |
|
||
| 4 | внутренний сбой | дефект скрипта, доложить |
|
||
|
||
Различать 1 и 3 обязательно: «дрейф в беклоге» — рабочая ситуация, «каталога
|
||
нет» — нерабочая, и одинаковая реакция на них была бы неверна в обоих случаях.
|
||
|
||
Тип — английское ключевое слово `goal` / `idea` / `epic` / `task` (как и прочие
|
||
токены команд); `task` префикса не несёт, остальные кодируются `[goal]`/
|
||
`[idea]`/`[epic]` в заголовке. Текст задачи при этом русский.
|
||
|
||
**Мутации правят файл и индексы заодно** — руками строку индекса или мета-строку
|
||
не пиши, зови `add`/`edit`/`move`/`close`/`sprint`. Смена заголовка, хука, типа,
|
||
цели и **тегов** — это `edit`: он держит H1, мета-строку и индекс в синхроне.
|
||
Снятие тега — `--rm-tag` (после ответа на вопрос снимается `question`), смена
|
||
цели — `--goal`, он заменяет прежний `goal:*`.
|
||
|
||
**Переезд между индексами — следствие смены типа, а не отдельная команда.**
|
||
`edit <slug> --type goal --section <часть плана>` переносит строку из
|
||
`BACKLOG.md` в `PLAN.md` (и обратно `--type task --section <секция беклога>`);
|
||
`move` двигает только внутри одного индекса и пишет причину. `--section` у
|
||
`edit` работает **только** при таком переезде — иначе он отсылает к `move`,
|
||
потому что смена секции без причины и есть тот дрейф, который потом никто не
|
||
объяснит. Задача в наборе спринта тип не меняет вовсе: сперва `sprint drop`.
|
||
|
||
Тело задачи скрипт не трогает:
|
||
`add` кладёт заголовок, мета-строку и шаблон с подсказками, тело дописываешь
|
||
редактором (пока плейсхолдер на месте, `check` напоминает).
|
||
|
||
`check` — единственный судья согласованности; что именно он ловит, скажет его
|
||
вывод, здесь не пересказываем. Гоняй его **в начале сессии** и **после каждой
|
||
правки**, даже если правил мутациями: дрейф мог накопиться раньше. Накопившееся
|
||
чини `check --fix` — он детерминированно правит то, где истина однозначна
|
||
(секция, заголовок, дубли, хук из индекса в файл, строка в чужом индексе,
|
||
пометка `decomposed` у цели с задачами), а неоднозначное (ссылка на исчезнувший
|
||
файл, задача сразу в двух индексах) печатает отдельной пометкой
|
||
`НЕОДНОЗНАЧНО` — это тебе, и это идёт строкой доклада.
|
||
|
||
`--fix` правит **и файлы** — ровно в двух местах, где источник ровно один и
|
||
выбирать не из чего: хук, оставшийся только в индексе, переезжает в мета-строку,
|
||
и цель, у которой есть задачи, получает тег `decomposed`. Оба случая печатаются
|
||
поимённо.
|
||
|
||
**Что механизировано, а что нет.** Критерии приёмки проверяются у задачи, взятой
|
||
в набор (`sprint take` и `check` по задачам спринта): число пунктов — жёстко
|
||
(меньше двух — отказ, больше пяти — замечание), наличие оракула — **эвристикой**
|
||
по слову «оракул» в пункте. Настоящий оракул от слова «оракул» машина не
|
||
отличает, поэтому эвристика даёт только замечание, и в докладе это называется
|
||
как есть: «проверено число пунктов, годность оракулов — глазами».
|
||
|
||
Формат файла, мета-строки, слага, индексов и `REJECTED.md` —
|
||
[references/task-format.md](references/task-format.md). Там же тест «готова к
|
||
взятию» и требования к критериям приёмки.
|
||
|
||
## Сценарии
|
||
|
||
### Завести задачу, идею или цель из диалога
|
||
|
||
1. **Фильтр.** Делаем прямо сейчас — не заводим. Не пожалеем о потере — не
|
||
заводим. Родилось три кандидата — покажи их и спроси, какие заводить: молча
|
||
заведённая пачка и есть тот самый отказ из правила 1.
|
||
2. **Дедуп.** `list` плюс поиск по слагам, хукам и телам (`grep -ril`),
|
||
**включая `REJECTED.md`**. Нашлось среди живых — **дописываем в существующий
|
||
файл**, а не заводим соседний. Нашлось в `REJECTED.md` — покажи пользователю
|
||
ту строку и что изменилось с момента отказа (`add` предупредит и сам, но
|
||
молча заводить нельзя). Две задачи об одном — самая дорогая находка
|
||
переоценки.
|
||
3. **Тип по тесту готовности** (см. task-format): проходит — задача, не
|
||
проходит — идея (`--type idea`), проходит по пользе, но не делается одним
|
||
заходом — эпик (`--type epic`, сперва декомпозиция). Направление, а не
|
||
работа — цель (`--type goal`).
|
||
4. **Цель задачи.** У каждой задачи должен быть `--goal <слаг>`: задача вне цели
|
||
не попадёт ни в один спринт. Подходящей цели нет — либо она заводится
|
||
(`--type goal` кустом), либо это сигнал, что задача никому не служит и
|
||
заводить её не надо. У идеи цели может не быть — она проставляется, когда
|
||
идея становится задачей.
|
||
5. `add …`, затем допиши тело редактором: одна фраза, критерии приёмки с
|
||
оракулами, рамки. Хук отвечает «почему это лежит в беклоге» — состояние,
|
||
остаток, боль, — а не пересказывает первый абзац, и пишется **для человека**:
|
||
не «канонизация внутри транзакции», а «тело 40 МиБ держит блокировку 5 секунд,
|
||
соседние доставки уходят в отказ».
|
||
6. `check`.
|
||
|
||
### Разобрать находки аудита или ревью
|
||
|
||
Ревью и аудиты — тоже источник задач, но с зеркальной диалогу опасностью: не
|
||
пять файлов из одной мысли, а сорок файлов из сорока сырых находок. Защита та
|
||
же, что в самом ревью: кластеризация по причине, дедуп против живых и
|
||
`REJECTED.md`, находка без свидетельства → идея, а не задача, и карта кластеров
|
||
пользователю до создания файлов. Порядок, отображение серьёзности и привязка к
|
||
целям — [references/from-review.md](references/from-review.md).
|
||
|
||
### Прийти в репозиторий, где задачи уже как-то ведутся
|
||
|
||
Разовая операция: вывести каталог задач из старой раскладки беклога, `TODO.md`,
|
||
заметок или списка шагов в плане — скилл `adopt`. Сюда же относится
|
||
переименование транслитных слагов в английские: оно делается **одним проходом
|
||
вместе с починкой перекрёстных ссылок**, а не по одному слагу.
|
||
|
||
### Декомпозиция и штурм идеи
|
||
|
||
[references/split.md](references/split.md). Обе операции превращают одну запись в
|
||
несколько, и у обеих есть проверяемый тест: части должны **мерджиться порознь** и
|
||
**каждая давать видимую пользу**, а у штурма исход «выкинуть» — полноправный.
|
||
|
||
### Гигиена полей
|
||
|
||
Правится по ходу любой операции, которая задачи касается (но не «заодно» по
|
||
всему беклогу):
|
||
|
||
- **протухший хук** — задача изменилась, а хук отвечает на старый вопрос;
|
||
особенно после ответа на вопрос задачи: «Решено: …» на «почему это лежит в
|
||
беклоге» уже не отвечает. Переписывается `edit <slug> --hook …` — он правит
|
||
мета-строку файла и строку индекса заодно;
|
||
- **вопрос, застрявший в прозе** — вынимается в раздел «Вопросы» плюс тег
|
||
`question` (`edit --add-tag question`), иначе он не виден ни `list
|
||
--questions`, ни правилу «задача с открытым вопросом в набор не берётся»;
|
||
- **тег, который некому снять** — `question` после ответа снимается `edit
|
||
--rm-tag question` вместе с записью ответа в тело;
|
||
- **свойство репозитория в рамках** — номер миграции, хеш, версия зависимости:
|
||
в лежалой задаче протухает молча и становится ложной рамкой. Снимается;
|
||
снимок берётся при постановке, а не при заведении;
|
||
- **предписание процесса в теле** — «делать таким-то профилем ревью», «взять
|
||
такой-то агент»: это второй дом для правила выбора и путь понизить требования
|
||
решением, принятым до проектирования. Снимается.
|
||
|
||
## Переносимость
|
||
|
||
Скилл независим от **языка программирования, сборки, CI и трекера**: он ничего
|
||
не знает ни про Go, ни про npm, ни про конкретный багтрекер — задачи для него
|
||
просто каталог markdown. Текст задач — русский (язык документации проекта);
|
||
зашита только латиница слага.
|
||
|
||
- **Каталог задач** ищется цепочкой: `--dir` → **указатель в `CLAUDE.md`
|
||
проекта** (его читаешь ты и передаёшь `--dir`; скрипт чужую документацию не
|
||
разбирает) → `.tasks.json` вверх от текущего каталога → умолчания
|
||
(`docs/tasks`, `tasks`, `doc/tasks`) вверх от текущего каталога, до корня
|
||
репозитория. Не нашлось — код 3 и вопрос человеку, а не догадка: `init`
|
||
заводит каталог **только** когда проект действительно новый.
|
||
**В примерах `--dir` стоит намеренно:** каталог вне умолчаний иначе не
|
||
находится, а вызов из подкаталога — обычное дело.
|
||
- **Секции беклога** берутся из заголовков `##` индекса как есть; их количество
|
||
и названия — дело проекта (умолчание `ядро` / `инфра`).
|
||
- **Имена индексов и подкаталога** — параметры `init`, живут в
|
||
`<tasks>/.tasks.json`. Ничего не зашито именем файла.
|
||
|
||
### Вызов из другого плагина
|
||
|
||
`$CLAUDE_PLUGIN_ROOT` раскрывается **только внутри своего плагина**: чужой
|
||
контекст — пайплайн задачи, конвейер ревью, любой другой скилл — до `tasks.py`
|
||
по этой переменной не дотянется. Поэтому контракт такой:
|
||
|
||
> **Проект называет команду учёта задач в своём `CLAUDE.md`** — целиком, готовой
|
||
> к запуску строкой (слот 6 ниже). Вызывающий берёт её оттуда. Слота нет —
|
||
> вызывающий **не выдумывает путь и не правит индекс руками**, а сообщает в
|
||
> докладе, что закрытие/заведение остаётся за владельцем задач.
|
||
|
||
Так вызывающему не нужно знать ни про плагин, ни про его расположение: он знает
|
||
проект, а проект знает команду.
|
||
|
||
## Слоты проекта
|
||
|
||
Скилл не знает ни языка программирования, ни сборки, ни CI, ни трекера — задачи
|
||
для него просто каталог markdown. Всё проектное живёт в `CLAUDE.md` проекта, и
|
||
**проект обязан дописать туда**:
|
||
|
||
1. **Путь каталога задач**, если он не `docs/tasks`, и **секции беклога** — по
|
||
умолчанию `ядро` / `инфра`; граница между ними режется по существу работы, а
|
||
не по её поводу. Имена индексов и подкаталога, если они другие, задаются
|
||
`init` и живут в `<tasks>/.tasks.json`.
|
||
2. **Что такое «сделана»** — чем задача выполняется (пайплайн проекта) и что
|
||
входит в его определение готовности. Скилл требует лишь **форму**: пайплайн
|
||
проекта пройден + критерии приёмки проверены поимённо.
|
||
3. **Куда переезжает суть реализованной задачи** — спеки, ADR, архив изменений:
|
||
без этого не проверить, что задача закрыта не коммитом, а решением.
|
||
4. **Что считается необратимым** и потому спрашивается у человека всегда
|
||
(деплой, выкладка наружу, удаление или перезапись данных).
|
||
5. **Оракулы, которые в проекте вообще есть** — чем проверяется критерий
|
||
приёмки: тест, команда, прогон на реальных данных, глазами по логу.
|
||
6. **Команда учёта задач** — готовая строка, которой чужой контекст зовёт
|
||
`tasks.py`, потому что путь к плагину ему неизвестен. Например:
|
||
|
||
```
|
||
Команда учёта задач: python3 ~/.claude/plugins/marketplaces/av-dev-skills/\
|
||
av-dev-tasks/skills/tasks/scripts/tasks.py --dir docs/tasks
|
||
```
|
||
|
||
Слот заполняется один раз при подключении плагина. Он же отвечает на вопрос
|
||
«кто закрывает задачу»: команду знает проект, зовёт её владелец спринта.
|
||
|
||
Ничего из этого скилл не угадывает: не нашёл — спрашивает пользователя, а не
|
||
подставляет умолчание.
|
||
|
||
## Общее для всех сценариев
|
||
|
||
- **Развилки — пользователю.** Через `AskUserQuestion`, с уже сформулированным
|
||
предварительным суждением (**рекомендация — первым вариантом**). Что выкинуть,
|
||
под какую цель отнести, какая рамка идеи верна — решение пользователя. Слаг,
|
||
формулировка, порядок строк в индексе — механика, делаем сами.
|
||
- **Не больше трёх вопросов за раз.** Пачка длиннее трёх тяжела для ответа;
|
||
решений больше — веди **несколько итераций** диалога по ≤3, а не один
|
||
перегруженный запрос. Между итерациями применяй уже решённое.
|
||
- **Границы покрытия в отчёте.** Любая сессия разбора, штурма или интейка
|
||
заканчивается строкой «просмотрено N из M, не трогали — …». Отчёт без неё
|
||
сообщает «беклог разобран», не сообщая, какая его часть осталась нетронутой.
|
||
- **Ничего не удаляем молча.** Файл исчезает только через `close` — `--reason`
|
||
(ушла без реализации) или `--implemented` (реализована). Прямого `rm` нет.
|
||
- **Слаги английские**, kebab-case, не транслит: `tie-break-equal-completeness`,
|
||
а не `taj-brejk-pri-ravnoj-polnote`. Заголовки, тела и хуки — русские.
|
||
|
||
## Чего этот скилл не делает
|
||
|
||
Не пишет код, не заводит спеки и предложения об изменении, не берёт задачу в
|
||
работу — этим занимается пайплайн проекта. Не ведёт спринт и не проводит сессию
|
||
между спринтами — это `session`. Не решает за пользователя, что важно. Не
|
||
переоформляет существующие задачи «заодно»: правится то, чего касается операция.
|