av-dev-pm: плагин переименован, заведены канон документов и скиллы init/canon/docs
- av-dev-tasks → av-dev-pm; канон определён единственным reference-файлом, который читают все три новых скилла - canon: check/adopt/upgrade плюс docs.py — раскладка, битые ссылки, версия, маркеры долга, сверки миграций и capability с документацией - tasks и session: путь docs/tasks жёсткий, конфиг переехал в docs/.pm.json, слот «Команда учёта задач» убран в пользу вызова скилла, раздел «Стимулы» переписан под совпавших приёмщика и исполнителя
This commit is contained in:
@@ -0,0 +1,331 @@
|
||||
---
|
||||
name: tasks
|
||||
description: Ведение задач и целей как каталога markdown-файлов (одна запись = один файл в items/ + строка в одном из индексов). Заведение задачи, идеи или цели из диалога, разбор находок аудита/ревью, декомпозиция на независимо полезные части, мозговой штурм идеи, гигиена полей и проверка согласованности индексов. Использовать, когда просят добавить задачу/идею/цель, превратить находки ревью в задачи, разбить задачу, проработать идею, поправить формат или проверить беклог. Ритуал между спринтами — скилл session. Не реализует задачи — этим занимается пайплайн проекта.
|
||||
---
|
||||
|
||||
# Задачи
|
||||
|
||||
Задачи — каталог markdown-файлов. Одна запись = один файл `items/<slug>.md` плюс
|
||||
строка **ровно в одном** индексе. Скилл владеет **форматом и содержимым**:
|
||||
заводит, редактирует, закрывает, разбирает находки ревью, дробит, штурмует идеи.
|
||||
|
||||
Чем он **не** владеет: ритуалом между спринтами (разбор вопросов → разбор
|
||||
прошедшего спринта → переоценка → выбор цели и набор) — это скилл `session`; и
|
||||
выполнением задачи — это пайплайн проекта.
|
||||
|
||||
## Четыре правила, из которых всё следует
|
||||
|
||||
Ситуация не покрыта инструкцией — решай по ним.
|
||||
|
||||
1. **Беклог гниёт с той стороны, где его пополняют.** Заведение — самая частая
|
||||
операция и с худшим отказом: из одного разговора рождается пять файлов, а
|
||||
переоценка потом разгребает то, чего не надо было заводить. Дедупликация и
|
||||
фильтр на входе дешевле любой чистки. Заводим только то, что **не делаем
|
||||
сейчас** и о потере чего пожалеем.
|
||||
2. **Файл — источник истины, индексы производны.** Разошлись — неправы индексы.
|
||||
Согласованность механизируема и проверяется командой, а не вниманием: всё,
|
||||
что ловит `tasks.py check`, не должно попадать ни в чек-лист, ни в промпт.
|
||||
Поэтому **хук живёт в мета-строке файла**, а строка индекса его лишь
|
||||
повторяет: пока хук лежал только в индексе, восстановление пропавшей строки
|
||||
теряло его молча и навсегда. Единственное исключение намеренное: **в каком
|
||||
индексе лежит задача, знают индексы** — «в спринте» это свойство спринта, а
|
||||
не файла, поля-состояния нет.
|
||||
3. **Причина переживает запись.** Выкинутая без причины задача вернётся через
|
||||
квартал тем же текстом. Реализованная оставляет след в коммите — выкинутая не
|
||||
оставляет ничего, поэтому у неё есть `REJECTED.md`.
|
||||
4. **Порядка нет, есть цель.** Ни в секциях, ни списком: «что делать дальше»
|
||||
отвечает набор спринта, а между спринтами порядок не нужен никому — брать
|
||||
задачи вне спринта запрещает заморозка. Поэтому нет ни приоритетов, ни
|
||||
«повысить», ни «встать раньше»: вместо повышения — смена цели или включение
|
||||
в набор.
|
||||
|
||||
## Раскладка
|
||||
|
||||
Каталог задач — **`docs/tasks`, жёстко**: это часть
|
||||
[канона документов](../canon/references/canon.md), и подгоняется под него
|
||||
проект, а не наоборот.
|
||||
|
||||
```
|
||||
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 docs/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` —
|
||||
`docs/tasks` от корня проекта. `--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 … # разовая адаптация, references/adopt.md
|
||||
```
|
||||
|
||||
**Коды выхода — единый словарь; на нём ветвятся скиллы, а не на тексте вывода:**
|
||||
|
||||
| Код | Что случилось | Что делать |
|
||||
| --- | --- | --- |
|
||||
| 0 | сошлось / сделано | дальше по сценарию |
|
||||
| 1 | **только `check`:** найден дрейф индексов и файлов | `check --fix`, остаток разобрать |
|
||||
| 2 | ошибка употребления: аргументы или нарушенное правило | читать сообщение, это отказ по существу |
|
||||
| 3 | окружение: каталог не найден, конфиг битый или мимо диска | чинится путём или `docs/.pm.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`,
|
||||
заметок или списка шагов в плане — [references/adopt.md](references/adopt.md).
|
||||
Сюда же относится переименование транслитных слагов в английские: оно делается
|
||||
**одним проходом вместе с починкой перекрёстных ссылок**, а не по одному слагу.
|
||||
|
||||
Если переводить надо не только задачи, а весь `docs/` — это скилл
|
||||
`av-dev-pm:canon`, и он зовёт этот сценарий сам на своём шаге.
|
||||
|
||||
### Декомпозиция и штурм идеи
|
||||
|
||||
[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. Текст задач — русский (язык документации проекта);
|
||||
зашита только латиница слага. OpenSpec ему тоже не нужен.
|
||||
|
||||
- **Каталог задач — `docs/tasks`, жёстко.** Цепочки разрешения нет: раскладка
|
||||
канона одинакова во всех проектах, и искать больше нечего. Каталога нет — код
|
||||
3 и вопрос человеку; `init` заводит его **только** когда проект действительно
|
||||
новый, а перевод чужой раскладки делает `av-dev-pm:canon`.
|
||||
- **Настройки живут в `docs/.pm.json`**, ключ `tasks`: секции беклога и имена
|
||||
индексов, если они отличаются от умолчания. Один конфиг на весь канон, а не по
|
||||
одному на каталог.
|
||||
- **Секции беклога** берутся из заголовков `##` индекса как есть; их количество
|
||||
и названия — дело проекта (умолчание `ядро` / `инфра`).
|
||||
|
||||
### Вызов из другого плагина
|
||||
|
||||
`$CLAUDE_PLUGIN_ROOT` раскрывается **только внутри своего плагина**: пайплайн
|
||||
задачи, конвейер ревью и любой другой чужой контекст до `tasks.py` по этой
|
||||
переменной не дотянутся. Мост — **вызов скилла через пространство имён**, а не
|
||||
путь:
|
||||
|
||||
> Чужой контекст зовёт `Skill av-dev-pm:tasks` и называет, что нужно сделать
|
||||
> («закрой задачу `<слаг>`, реализована»). Скилл разрешает свой
|
||||
> `$CLAUDE_PLUGIN_ROOT` сам. Путь наружу не выносится вовсе.
|
||||
|
||||
Плагина в проекте нет — вызов не разрешится, и вызывающий **не выдумывает путь и
|
||||
не правит индекс руками**, а сообщает в докладе, что учёт задач остаётся за
|
||||
владельцем.
|
||||
|
||||
## Слоты проекта
|
||||
|
||||
Почти всё, что скиллу нужно знать о проекте, отвечает канон структурой: куда
|
||||
переезжает суть реализованной задачи — `openspec/specs`, `adr/`, архив change;
|
||||
какие в проекте оракулы — семантика гейта в `CLAUDE.md`. Отдельными слотами
|
||||
остаётся то, чего из раскладки не вывести. **Проект дописывает в `CLAUDE.md`**:
|
||||
|
||||
1. **Что такое «сделана»** — чем задача выполняется (пайплайн проекта) и что
|
||||
входит в его определение готовности. Скилл требует лишь **форму**: пайплайн
|
||||
проекта пройден + критерии приёмки проверены поимённо.
|
||||
2. **Что считается необратимым** и потому спрашивается у человека всегда
|
||||
(деплой, выкладка наружу, удаление или перезапись данных).
|
||||
|
||||
Ни того ни другого скилл не угадывает: не нашёл — спрашивает пользователя, а не
|
||||
подставляет умолчание.
|
||||
|
||||
## Общее для всех сценариев
|
||||
|
||||
- **Развилки — пользователю.** Через `AskUserQuestion`, с уже сформулированным
|
||||
предварительным суждением (**рекомендация — первым вариантом**). Что выкинуть,
|
||||
под какую цель отнести, какая рамка идеи верна — решение пользователя. Слаг,
|
||||
формулировка, порядок строк в индексе — механика, делаем сами.
|
||||
- **Не больше трёх вопросов за раз.** Пачка длиннее трёх тяжела для ответа;
|
||||
решений больше — веди **несколько итераций** диалога по ≤3, а не один
|
||||
перегруженный запрос. Между итерациями применяй уже решённое.
|
||||
- **Границы покрытия в отчёте.** Любая сессия разбора, штурма или интейка
|
||||
заканчивается строкой «просмотрено N из M, не трогали — …». Отчёт без неё
|
||||
сообщает «беклог разобран», не сообщая, какая его часть осталась нетронутой.
|
||||
- **Ничего не удаляем молча.** Файл исчезает только через `close` — `--reason`
|
||||
(ушла без реализации) или `--implemented` (реализована). Прямого `rm` нет.
|
||||
- **Слаги английские**, kebab-case, не транслит: `tie-break-equal-completeness`,
|
||||
а не `taj-brejk-pri-ravnoj-polnote`. Заголовки, тела и хуки — русские.
|
||||
|
||||
## Чего этот скилл не делает
|
||||
|
||||
Не пишет код, не заводит спеки и предложения об изменении, не берёт задачу в
|
||||
работу — этим занимается пайплайн проекта. Не ведёт спринт и не проводит сессию
|
||||
между спринтами — это `session`. Не решает за пользователя, что важно. Не
|
||||
переоформляет существующие задачи «заодно»: правится то, чего касается операция.
|
||||
@@ -0,0 +1,122 @@
|
||||
# Адаптация каталога задач
|
||||
|
||||
Проект, где задачи уже как-то ведутся, и из имеющегося материала **выводится**
|
||||
заполненный каталог задач: цели, задачи, кладбище, индексы. Операция разовая —
|
||||
после неё проект живёт скиллами `tasks` и `session`.
|
||||
|
||||
**Это часть приведения проекта к канону.** Раскладку `docs/` целиком ведёт скилл
|
||||
`av-dev-pm:canon`; он же зовёт этот сценарий на шаге «каталог задач», потому что
|
||||
форматом задач владеет `tasks`, а не `canon`. Отдельно сценарий вызывается,
|
||||
когда переводить надо **только** задачи.
|
||||
|
||||
Вход какой угодно: старая раскладка `av-dev-backlog` (индекс `README.md`,
|
||||
кладбище `CLOSED.md`, приоритеты секциями, транслитные слаги, файлы рядом с
|
||||
индексом), `TODO.md`, россыпь заметок, раздел «планы» в `README.md`, список
|
||||
шагов в плане проекта.
|
||||
|
||||
## Три правила, из которых всё следует
|
||||
|
||||
1. **Сперва карта, потом файлы.** Человеку показывается, что найдено, как
|
||||
разложилось по целям и **что не разложилось**, — и только после подтверждения
|
||||
пишется хоть один файл. Это то же правило, что у интейка находок ревью:
|
||||
массовое заведение записей без подтверждения — самый дорогой отказ, потому
|
||||
что разгребает его потом переоценка.
|
||||
2. **Ничего не терять.** Исходный текст переезжает в тело, хук и причина
|
||||
сохраняются, кладбище переносится строка в строку. Переименование слага —
|
||||
не правка, а **перенос ссылок**: он делается одним проходом вместе с
|
||||
переименованием, иначе останутся битые ссылки, которых никто не проверяет.
|
||||
3. **Что не классифицировалось — назвать поимённо.** Проглоченный пункт
|
||||
выглядит как «всё перенеслось». Список «не разложилось» идёт в доклад
|
||||
целиком, с причиной по каждому пункту.
|
||||
|
||||
## Форма: карта — суждение — запись
|
||||
|
||||
Механику несёт `tasks.py adopt`, суждение — ты. Разделено ровно по границе
|
||||
«машина умеет / не умеет»:
|
||||
|
||||
```
|
||||
tk="$CLAUDE_PLUGIN_ROOT/skills/tasks/scripts/tasks.py"
|
||||
|
||||
python3 $tk adopt scan --from docs/backlog docs/plan.md TODO.md \
|
||||
--target docs/tasks --out tasks-adopt-plan.json # только чтение
|
||||
python3 $tk adopt apply --plan tasks-adopt-plan.json \
|
||||
--refs docs openspec CLAUDE.md README.md # запись
|
||||
```
|
||||
|
||||
`scan` ничего не пишет, кроме карты: он распознаёт раскладку, собирает записи,
|
||||
хуки, причины, кладбище, помечает похожее на транслит и на открытый вопрос в
|
||||
прозе, и **называет поимённо** то, что не разложилось. `apply` пишет каталог
|
||||
целиком одним проходом и чинит перекрёстные ссылки.
|
||||
|
||||
Между ними — твоя работа, которую машина не сделает:
|
||||
|
||||
- **английские слаги.** Перевести `taj-brejk-pri-ravnoj-polnote` в
|
||||
`tie-break-equal-completeness` может только тот, кто понимает смысл. `scan`
|
||||
честно говорит: проверить надо **все** слаги, признаки транслита — эвристика;
|
||||
- **цели.** Шаги плана — готовые цели **линии** (порядок и обоснование у них уже
|
||||
есть); тематические скопления задач — **кусты** («прочность слияния», «журнал
|
||||
и пересборка»). Предлагаешь ты, назначает человек;
|
||||
- **что вообще не задача.** Обоснование порядка шагов, абзац прозой, заголовок
|
||||
раздела — это не пункты беклога, и они уходят в «не разложилось» с причиной.
|
||||
|
||||
## Порядок
|
||||
|
||||
1. **Осмотрись.** Где лежат задачи, план, заметки. Каталог задач по канону —
|
||||
всегда `docs/tasks`. Секции беклога (`--sections`) — по умолчанию
|
||||
`ядро,инфра`; если у проекта деление другое по существу, оно называется
|
||||
здесь, а не подгоняется под умолчание, и уезжает в `docs/.pm.json`.
|
||||
2. **`adopt scan`** по всем источникам разом. Один прогон, одна карта: два
|
||||
прохода дадут два несогласованных состояния.
|
||||
3. **Заполни карту**: `slug` (английский), `section`, `goal` у каждой записи;
|
||||
список `goals` — из шагов плана и из кустов. Закрытый шаг плана целью не
|
||||
заводится. Пустой `goal` — законный исход только у идеи.
|
||||
4. **Покажи человеку карту** через `AskUserQuestion`, ≤3 вопроса за итерацию,
|
||||
рекомендация первым вариантом. Показывается: сколько записей, предлагаемые
|
||||
цели (линия и кусты) с обоснованием, спорные отнесения, список «не
|
||||
разложилось». Массовые механические решения (слаги, порядок строк) не
|
||||
выносятся — это механика.
|
||||
5. **`adopt apply`.** `--refs` перечисляет **всё**, где могут стоять ссылки на
|
||||
слаги: документация, архив изменений, `CLAUDE.md`, `README.md`. Скрипт
|
||||
посчитает и покажет, сколько ссылок поправлено и по каким слагам.
|
||||
6. **`tasks.py check`** и доклад.
|
||||
|
||||
`apply` отказывается писать поверх живого каталога и проверяет карту целиком
|
||||
**до** первой записи: неверная секция, дубль слага, цель, которой нет в карте —
|
||||
всё это отказ до того, как на диске появился хотя бы один файл.
|
||||
|
||||
## Переходное состояние — объявляется, а не заминается
|
||||
|
||||
Сразу после адаптации задачи в большинстве своём **не готовы к взятию**: у них
|
||||
нет критериев приёмки, а у части может не быть цели. Это нормально, но обязано
|
||||
быть названо, иначе следующий агент примет пустой беклог за поломку.
|
||||
|
||||
`apply` печатает состояние по факту: сколько задач без цели (это **ошибки**
|
||||
`check`) и сколько без критериев (`check` их ошибкой не считает, но `sprint
|
||||
take` такую задачу не возьмёт). Закрывается это **порциями переоценки** — шаг 3
|
||||
скилла `session`, 5–8 задач за порцию: проставить цели, превратить «готово,
|
||||
когда» в критерии с оракулами, вынуть вопросы из прозы в раздел «Вопросы».
|
||||
|
||||
Готовность к первому спринту — не «`check` зелёный», а «есть 2–5 критериев хотя
|
||||
бы у набора под одну цель».
|
||||
|
||||
## Чего адаптация не делает
|
||||
|
||||
- **Не удаляет источники.** Старый каталог остаётся на месте: сверить и убрать —
|
||||
дело человека, удалять чужое молча нельзя. В доклад идёт готовая команда.
|
||||
- **Не переписывает подписи ссылок.** `[docs/backlog](docs/tasks/BACKLOG.md)` —
|
||||
цель поправлена, текст остался; это правится глазами, и таких мест немного.
|
||||
- **Не сочиняет критерии приёмки и не придумывает цели**, которых в материале
|
||||
нет. Придуманная цель хуже отсутствующей: под неё соберут спринт.
|
||||
- **Не трогает историю.** В коммитах старые слаги остаются, и это нормально.
|
||||
|
||||
## Доклад
|
||||
|
||||
- Источники и что в каждом распознано (раскладка, индекс, кладбище, секции).
|
||||
- Сколько записей перенесено, сколько целей заведено (линия / кусты) и откуда
|
||||
каждая выведена.
|
||||
- **Переименования**: сколько слагов, сколько ссылок поправлено и в скольких
|
||||
файлах — числом, а не «поправлены ссылки».
|
||||
- **Не разложилось**: поимённо, с причиной.
|
||||
- Переходное состояние: сколько задач без цели, сколько без критериев, чем и за
|
||||
сколько порций закрывается.
|
||||
- `tasks.py check` — результат строкой.
|
||||
@@ -0,0 +1,102 @@
|
||||
# Задачи из аудита и ревью
|
||||
|
||||
Ревью и аудиты — код-ревью, архитектурный проход, аудит безопасности, любой
|
||||
разбор другим агентом — порождают находки, часть которых становится задачами.
|
||||
Это отдельный интейк со своей опасностью, **зеркальной** интейку из диалога.
|
||||
|
||||
- Интейк из диалога грешит переполнением: из одной мысли рождается пять файлов.
|
||||
- Интейк из ревью грешит сваливанием: сорок сырых находок превращаются в сорок
|
||||
файлов. Беклог раздувается, а следующая переоценка склеивает их обратно.
|
||||
|
||||
Защита от сваливания — та же, что в самом ревью: **кластеризация по причине, а
|
||||
не файл-на-находку.** Если у ревью был триаж — половина работы уже сделана, бери
|
||||
его выход. Если нет — триажируй сам, прежде чем заводить.
|
||||
|
||||
## Находка агента — не задача
|
||||
|
||||
Мнение агента — **гипотеза, пока у неё нет свидетельства** (падающий тест,
|
||||
воспроизводимый шаг, положение гайда). Согласие нескольких находок само по себе
|
||||
достоверность не повышает: это один источник, высказавшийся несколько раз.
|
||||
|
||||
Отсюда фильтр входа, поверх обычного «не делаем сейчас + пожалеем о потере»:
|
||||
|
||||
- **Находка со свидетельством**, отложенная к исполнению → **задача**.
|
||||
Свидетельство и последствие переносим в тело — это её «почему», то самое, что
|
||||
переживает запись.
|
||||
- **Находка без свидетельства / низкой уверенности** → **идея** (`[idea]`), а не
|
||||
задача. Её судьба — штурм, где либо найдётся подтверждение, либо она уедет в
|
||||
`REJECTED.md`.
|
||||
- **Уже починено по ходу ревью** → **ничего**. Починенное не заводим.
|
||||
- **Развилка, решённая при ревью** → ничего; решённая «потом» → задача с
|
||||
вопросом в разделе «Вопросы» и тегом `question`.
|
||||
|
||||
## Порядок
|
||||
|
||||
1. **Возьми выход триажа, а не сырые находки.** Сырой отчёт — это симптомы до
|
||||
дедупликации; в нём одна причина размазана по нескольким строкам.
|
||||
2. **Кластеризуй по причине.** Пять находок об одном отсутствующем инварианте —
|
||||
одна задача, а не пять. Класс мелочи (nits, косметика) — **один пакетный
|
||||
файл** со списком пунктов, а не файл на каждую запятую.
|
||||
3. **Дедуп против живых задач и `REJECTED.md`.** Аудит переоткрывает уже
|
||||
заведённое и уже выкинутое. Нашлось среди живых — дописываем находку в
|
||||
существующий файл. Нашлось в `REJECTED.md` — это сигнал: причина отказа могла
|
||||
устареть, выноси пользователю, а не заводи молча заново.
|
||||
4. **Разложи по целям.** У каждой заводимой задачи должен быть `goal:<слаг>`.
|
||||
Половина находок ревью не служит ничему из линии продукта — их цель это
|
||||
**куст** («прочность слияния», «журнал и пересборка», «наблюдаемость»).
|
||||
Подходящего куста нет — заведи его целью (`add --type goal --section кусты`)
|
||||
в том же проходе: без цели задача не попадёт ни в один спринт, а значит не
|
||||
будет сделана никогда.
|
||||
5. **Покажи карту до создания файлов.** Кластер → задача / идея / строка в
|
||||
пакетный файл / уже заведено / отброшено, и под какую цель — пачкой через
|
||||
`AskUserQuestion`. Это тот же барьер, что и «три кандидата» в интейке из
|
||||
диалога: массовое заведение файлов без подтверждения — ровно тот отказ, ради
|
||||
которого интейк из ревью и выделен. Дешёвая мелочь по явному согласию может
|
||||
заводиться и без поштучного вопроса — но карта пользователю предъявляется
|
||||
всё равно.
|
||||
6. **Заводи утверждённое** через `tasks.py add`, с двумя добавками:
|
||||
- **тег партии** — `--tag review-ГГГГ-ММ-ДД` (или `audit-<тема>`), чтобы весь
|
||||
заход разбора поднимался одной командой `list --tag …`;
|
||||
- **провенанс в теле** — кто нашёл, каким проходом, с каким свидетельством.
|
||||
Без него через месяц не отличить проверенную находку от догадки.
|
||||
7. `tasks.py check`.
|
||||
|
||||
## Куда девается серьёзность, если приоритетов нет
|
||||
|
||||
Приоритетов нет, и отображать серьёзность некуда — но **выкидывать её нельзя**.
|
||||
Правило замены:
|
||||
|
||||
- **тяжёлая находка со свидетельством** → задача под ту цель, которой она
|
||||
угрожает, и **кандидат в ближайший набор**: серьёзность здесь превращается в
|
||||
довод при выборе цели следующего спринта, а не в уровень в файле. Довод
|
||||
записывается причиной в мета-строке (`--reason`), иначе к моменту набора его
|
||||
никто не вспомнит;
|
||||
- **находка, ломающая уже идущий спринт**, — не интейк вовсе: см. правило
|
||||
вторжения в скилле `session`. В беклог она падает, только если врываться не
|
||||
положено;
|
||||
- **низкая уверенность или нет свидетельства** → идея;
|
||||
- **мелочь** → строка в пакетный файл;
|
||||
- **уже починено / развилка решена сейчас** → ничего.
|
||||
|
||||
Словарей серьёзности много, и отображать их механически не на что: при сомнении
|
||||
— вопрос пользователю, а не догадка.
|
||||
|
||||
## Поимённая сверка
|
||||
|
||||
Интейк считается выполненным, только если **каждая** находка триажа получила
|
||||
исход: слаг заведённой задачи, ссылку на существующую, строку пакетного файла
|
||||
или запись «не заведена: причина». Нулевой урожай при непустом отчёте триажа
|
||||
виден сразу — и это единственный способ отличить «находок не было» от «не стал
|
||||
заводить». Список составляет не тот, кто отчитывается о заведении.
|
||||
|
||||
Границы покрытия отчёта — то, что ревью проверить **не смогло**, — не находки и
|
||||
в задачи не идут: у них нет предмета. Их место в докладе, не в беклоге.
|
||||
|
||||
## Доклад
|
||||
|
||||
- Источник (какое ревью/аудит, сколько находок на входе).
|
||||
- Свёрнуто в задачи: N кластеров из M находок, со слагами, целями и тегом партии.
|
||||
- Что не заведено и почему: починено инлайн, уже заведено, ушло в идеи, в
|
||||
`REJECTED.md`.
|
||||
- Поимённая сверка: находок на входе N, исход есть у N.
|
||||
- `tasks.py check`.
|
||||
@@ -0,0 +1,82 @@
|
||||
# Декомпозиция и мозговой штурм
|
||||
|
||||
Обе операции превращают одну запись в несколько (или в ноль). Разница во входе:
|
||||
декомпозиция дробит **готовую задачу или эпик**, штурм прорабатывает **идею**,
|
||||
которая ещё не задача.
|
||||
|
||||
## Тест декомпозиции
|
||||
|
||||
Задачу можно дробить, только если части удовлетворяют **обоим** условиям:
|
||||
|
||||
1. **Мерджатся независимо.** Часть Б не требует, чтобы часть А была уже влита.
|
||||
Есть порядок «сперва А, потом Б, иначе не собрать» → это не декомпозиция, а
|
||||
план реализации: шаги остаются **внутри одного файла**.
|
||||
2. **Каждая даёт видимую пользу.** Часть, полезная только в комплекте с другой,
|
||||
— не самостоятельная задача. Пользу проверяй тестом «готова к взятию»
|
||||
(task-format): что станет наблюдаемо иначе именно от этой части и какие у неё
|
||||
собственные критерии приёмки.
|
||||
|
||||
Не проходит хотя бы одно — **не дроби**. Ложная декомпозиция плодит файлы,
|
||||
которые нельзя взять поодиночке, и переоценка потом склеивает их обратно.
|
||||
|
||||
**Цель наследуется.** Все части несут `goal:` родителя: декомпозиция не меняет
|
||||
того, чему работа служит. Если у части цель другая — это признак, что дробили не
|
||||
по той границе, либо что часть вообще из другой работы.
|
||||
|
||||
## Что делать с родителем
|
||||
|
||||
После разделения родитель **не остаётся** третьей висящей строкой:
|
||||
|
||||
- части полностью замещают его → `close <slug> --reason "разложена на a, b"`.
|
||||
`REJECTED.md` здесь — не «выкинули», а именно тот след, что переживает запись:
|
||||
через квартал вопрос «куда делась задача X» отвечается строкой со ссылками на
|
||||
наследников, а не археологией git;
|
||||
- родитель осмыслен как зонтик → `edit <slug> --type epic`, тело — ссылки на
|
||||
задачи-части, своих шагов у него нет. **Эпик не берётся в спринт** и живёт
|
||||
ровно до тех пор, пока не закрыта последняя часть.
|
||||
|
||||
Зонтик, который перестал быть временным и описывает направление, а не работу, —
|
||||
это уже **цель**, а не эпик. Тип на месте не меняется (цель живёт в другом
|
||||
индексе): заводится `[goal]` в `PLAN.md`, задачи получают `--goal <новый слаг>`,
|
||||
эпик закрывается с причиной-ссылкой.
|
||||
|
||||
## Когда декомпозиция случается посреди спринта
|
||||
|
||||
Задача, которая **переросла в эпик**, распознаётся до того, как под неё заведено
|
||||
предложение об изменении: иначе его придётся выбрасывать. Она помечается
|
||||
`[epic]`, выходит из набора (`sprint drop … --reason "переросла в эпик"`), уходит
|
||||
на декомпозицию, а спринт продолжается остальными. Части заводятся сразу, но в
|
||||
текущий набор **не добавляются** — набор заморожен.
|
||||
|
||||
## Мозговой штурм идеи
|
||||
|
||||
Идея (`[idea]`) не проходит тест «готова к взятию»: неясно, что именно делаем.
|
||||
Штурм проясняет — и это **generative-операция, а не applicative**.
|
||||
|
||||
Applicative-штурм («перечисли задачи, следующие из идеи») выдаёт очевидное:
|
||||
перечисляется то, что уже видно в формулировке. Ценное — на уровень выше.
|
||||
|
||||
1. **Сперва — формы, а не задачи.** Предложи **три разные постановки** идеи и
|
||||
назови **компромисс каждой**: что она даёт, чем платит, что оставляет за
|
||||
бортом. Если получилась одна постановка — штурм не состоялся, это
|
||||
applicative.
|
||||
2. **Вынеси формы пользователю** через `AskUserQuestion` с компромиссами. Рамку
|
||||
выбирает он: это продуктовое решение, не механика.
|
||||
3. **Назови цель.** Выбранная форма служит какой-то цели — существующей или
|
||||
новой. Идея, для которой цель не находится, скорее всего уезжает в
|
||||
`REJECTED.md`, а не заводится задачей.
|
||||
4. **Только выбранную форму** дроби по тесту декомпозиции выше и проставь
|
||||
критерии приёмки: без них наследники останутся идеями под другим именем.
|
||||
|
||||
**«Выкинуть» — полноправный исход штурма, а не его неудача.** Проработка, честно
|
||||
показавшая, что пользы нет или она несоразмерна цене, — это результат: идея
|
||||
уезжает с этой самой причиной, и та причина гасит её повторное появление.
|
||||
|
||||
## Доклад
|
||||
|
||||
- Идея/задача на входе, выбранная рамка (для штурма), задачи-наследники со
|
||||
слагами, целями и секциями.
|
||||
- Судьба родителя: удалён / стал эпиком / стал целью / выкинут с причиной.
|
||||
- `tasks.py check` после правок.
|
||||
- Границы покрытия: какие постановки рассмотрены и какие сознательно отброшены —
|
||||
чтобы штурм не пришлось повторять с нуля.
|
||||
@@ -0,0 +1,245 @@
|
||||
# Формат задач, целей и индексов
|
||||
|
||||
Заголовок, мета-строку и строку индекса ставит `tasks.py add` — руками их не
|
||||
пишут. Этот файл описывает, что именно скрипт создаёт и что проверяет `check`;
|
||||
тело задачи (одну фразу, критерии, рамки, контекст) дописывает агент.
|
||||
|
||||
## Файл задачи
|
||||
|
||||
`items/<slug>.md`:
|
||||
|
||||
```markdown
|
||||
# Тай-брейк при равной полноте
|
||||
|
||||
**Секция:** ядро — вышла из спринта: остаток писал нерешённое в журнал · **Хук:** порядок канонических форм берёт меньшее в 96% случаев — для накопительных это систематический недосчёт · **Теги:** goal:merge-robustness, sprint:2026-08-03
|
||||
|
||||
При столкновении точек выигрывает более полная, но при равной полноте побеждает
|
||||
последняя доставка — а она систематически беднее первой.
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- повторный прогон свёртки даёт тот же отпечаток состояния — оракул: команда сверки
|
||||
- накопительная метрика за сутки не уменьшается после повторной доставки — оракул: тест
|
||||
- в логе видно, какая из двух точек выиграла и почему — оракул: глазами по логу прогона
|
||||
|
||||
## Рамки
|
||||
|
||||
Схема не трогается; данные только читаются; перезапуск сервиса допустим.
|
||||
|
||||
Связано: решение о канонической форме содержимого.
|
||||
```
|
||||
|
||||
- **Заголовок H1** — он же заголовок строки в индексе, дословно. Тип кодируется
|
||||
префиксом `[goal]` / `[idea]` / `[epic]`; обычная задача — без префикса.
|
||||
Отдельного поля типа **нет**: два места для одного факта разъезжаются, а
|
||||
префикс виден прямо в индексе, где и принимается решение «брать или не брать».
|
||||
- **Мета-строка** — первая непустая строка после заголовка. Обязательна секция,
|
||||
причина после тире желательна (именно она объясняет, почему задача здесь
|
||||
оказалась — в том числе «вышла из спринта: …»), хук и теги опциональны. Поля
|
||||
разделяются ` · `, порядок свободный. `·` — служебный разделитель: в тексте
|
||||
причины и хука его быть не должно.
|
||||
- **Хук живёт здесь, а не только в индексе.** Строка индекса его повторяет и
|
||||
производна от него: `check` сверяет, `check --fix` восстанавливает пропавшую
|
||||
строку **вместе с хуком**. Пока хук лежал только в индексе, штатная починка
|
||||
дрейфа теряла его молча и навсегда — а хук это единственное, по чему задачу
|
||||
выбирают, не открывая.
|
||||
- **Тело** — одна фраза «что станет наблюдаемо иначе», критерии приёмки, рамки,
|
||||
контекст, ссылки. Пишется на языке документации проекта.
|
||||
|
||||
Тело — не план реализации и не спецификация: принятое и реализованное переезжает
|
||||
в документацию проекта, а файл задачи удаляется.
|
||||
|
||||
### Критерии приёмки
|
||||
|
||||
2–5 проверяемых утверждений **списком** `- …`, **у каждого назван оракул**. Не
|
||||
«работает корректно», а «повторный прогон даёт тот же отпечаток — оракул:
|
||||
команда сверки». Это не второе определение готовности, а проектная
|
||||
конкретизация вопроса «по чему видно, что закончено» из теста готовности ниже:
|
||||
там сказано «признак завершённости», здесь — «признак плюс чем проверяется».
|
||||
|
||||
**Что из этого механизировано.** `check` и `sprint take` считают пункты: меньше
|
||||
двух — отказ («— работает» одной строкой больше не проходит), больше пяти —
|
||||
замечание, обычно это признак, что задача крупнее задачи. Наличие оракула
|
||||
проверяется **эвристикой** — словом «оракул» в пункте, — и потому даёт только
|
||||
замечание: настоящий оракул от слова «оракул» машина не отличает, и делать вид,
|
||||
что проверено больше проверенного, хуже, чем не проверять вовсе.
|
||||
|
||||
**У идей критериев нет — именно поэтому они идеи.**
|
||||
|
||||
**Критерии — пол, но расхождение с ними есть дефект критериев.** Если приёмщик
|
||||
видит, что критерии закрыты, а суть задачи не достигнута, он **правит критерии и
|
||||
возвращает задачу исполнителю**, а не держит невидимое сверх-требование. Иначе
|
||||
исполнитель никогда не знает, закончил ли, и мотивирован занижать критерии
|
||||
заранее.
|
||||
|
||||
### Рамки
|
||||
|
||||
Одна строка: чего касаться нельзя, что перезапускается, что считается
|
||||
необратимым, трогается ли схема данных. **Свойства репозитория сюда не пишутся**
|
||||
— номер последней миграции, версия зависимости, хеш: в лежалой задаче они
|
||||
протухают молча и становятся ложной рамкой. Снимок берётся при постановке, а не
|
||||
при заведении.
|
||||
|
||||
### Вопросы
|
||||
|
||||
Неразобранное решение человека живёт разделом `## Вопросы` **плюс тегом
|
||||
`question`**. Раздел без тега или тег без раздела — дрейф, `check` о нём скажет.
|
||||
|
||||
**Судит факт, а не метка.** Отказ во взятии даёт **непустой раздел «Вопросы»**,
|
||||
независимо от того, стоит ли тег: иначе забывший тег проходил бы, а поставивший
|
||||
спотыкался — стимул ровно обратный записанному правилу. Тег производен: он нужен
|
||||
отбору снаружи файла (`list --questions`, `list --tag question`), и его
|
||||
отсутствие при непустом разделе — замечание, а не лазейка. Тег без раздела тоже
|
||||
отказ, но с другим советом: либо вопрос записан не туда, либо тег пора снять.
|
||||
|
||||
Ответ записывается в тело, тег снимается `edit <slug> --rm-tag question`, а хук
|
||||
переписывается: «Решено: …» на вопрос «почему это лежит в беклоге» уже не
|
||||
отвечает.
|
||||
|
||||
## Файл цели
|
||||
|
||||
```markdown
|
||||
# [goal] Прочность слияния
|
||||
|
||||
**Секция:** кусты · **Теги:** decomposed
|
||||
|
||||
Ради чего: точки из разных доставок сходятся в один часовой объект, и сегодня
|
||||
исход столкновения зависит от порядка доставки, а не от содержания.
|
||||
|
||||
## Завершение
|
||||
|
||||
Достигнута, когда исход слияния не зависит ни от порядка, ни от времени
|
||||
доставки, и это подтверждено повторным прогоном на живом корпусе.
|
||||
```
|
||||
|
||||
- **Задачи цели здесь не перечисляются.** Перечень даёт
|
||||
`tasks.py list --goal <слаг>`; хранимый список стал бы третьим индексом и
|
||||
поехал бы на первой же закрытой задаче.
|
||||
- **Раздел «Завершение»** — то, по чему видно, что цель достигнута.
|
||||
- **Тег `decomposed`** отличает «цель ещё не разобрана» от «все её задачи
|
||||
закрыты» — два состояния, у которых снаружи один и тот же признак: задач нет.
|
||||
Пометка именно **тегом**, а не строкой в теле: только так она проверяется.
|
||||
`check` требует его у цели без задач, `check --fix` сам ставит его цели, у
|
||||
которой задачи есть, а цель с тегом и без задач — прямое приглашение закрыть.
|
||||
- Цель живёт в `PLAN.md` и **никогда** — в `BACKLOG.md` или `SPRINT.md`.
|
||||
|
||||
## Слаг
|
||||
|
||||
Латиница и цифры, kebab-case, без ведущих, хвостовых и двойных дефисов
|
||||
(`foo-bar`, не `-foo`, `a--b`). Именуется **по сути, а не по текущей
|
||||
формулировке**: заголовок будет переписан при переоценке, а слаг стоит в ссылках
|
||||
из других задач, коммитов и черновиков. **Транслита не заводим** —
|
||||
`tie-break-equal-completeness`, а не `taj-brejk-pri-ravnoj-polnote`: транслит
|
||||
нечитаем для того, кто ищет по смыслу, и не сокращается.
|
||||
|
||||
Переименование слага — не правка, а перенос ссылок: делается одним атомарным
|
||||
проходом по всем местам, где слаг упомянут, иначе останутся битые ссылки,
|
||||
которых никто не проверяет.
|
||||
|
||||
## Индексы
|
||||
|
||||
Строка везде одной формы:
|
||||
|
||||
```markdown
|
||||
- [Заголовок дословно](items/slug.md) — хук
|
||||
```
|
||||
|
||||
Хук отвечает на «почему это лежит в беклоге» одним предложением: состояние,
|
||||
остаток, боль. Пересказ первого абзаца бесполезен — он уже есть по ссылке.
|
||||
|
||||
| Файл | Что отвечает | Секции |
|
||||
| --- | --- | --- |
|
||||
| `PLAN.md` | какие есть цели, в каком порядке идёт линия и почему | линия (упорядоченная) и кусты |
|
||||
| `BACKLOG.md` | что **можно взять** — только задачи | секции проекта (по умолчанию ядро/инфра) |
|
||||
| `SPRINT.md` | какая цель и какой набор под неё | одна: «Набор» |
|
||||
| `REJECTED.md` | что ушло без реализации и почему | — |
|
||||
|
||||
Секции — **единственные заголовки `##` в индексе**: любой другой `##` в
|
||||
преамбуле проверка сочтёт секцией. Внутри секции беклога порядок значения не
|
||||
имеет — порядка в беклоге нет вовсе.
|
||||
|
||||
**Секции «блокеры» среди них нет.** Блокер — состояние, а не полка: он живёт до
|
||||
ответа человека, а следы остаются вопросами в файлах задач распущенного спринта.
|
||||
Постоянно пустая секция со старой семантикой «разбираются пачками» противоречила
|
||||
бы правилу «эскалируем немедленно», поэтому `init` её не заводит, а `check`
|
||||
говорит о ней в чужом беклоге. Переезжаешь с такой секцией — удали её. В **линии** плана порядок значим и
|
||||
обосновывается прозой; двигают строку `move <slug> --section линия --after
|
||||
<другой>`.
|
||||
|
||||
Индексы **производны**: расходятся с файлом — правим индексы (`check --fix`).
|
||||
Строку руками не пишут.
|
||||
|
||||
Отсюда же ответ на «а если оборвётся посередине». Мутация сперва проверяет всё
|
||||
и складывает правки, и только потом пишет: сначала все временные файлы, потом
|
||||
переименования подряд. Полной транзакции на несколько файлов файловая система не
|
||||
даёт, но окно сжато до цепочки переименований, а **всё, что в нём может
|
||||
разъехаться, — производное**: файлы целы, индексы восстанавливает `check --fix`.
|
||||
Поэтому отказ на второй задаче из пяти не оставляет первую переписанной при
|
||||
нетронутых индексах.
|
||||
|
||||
`SPRINT.md` и есть артефакт заморозки: без него набор существует только в
|
||||
контексте сессии, и нарушение заморозки ненаблюдаемо.
|
||||
|
||||
## `REJECTED.md`
|
||||
|
||||
Туда уходит задача, покинувшая беклог **без реализации**. Строку пишет
|
||||
`tasks.py close --reason`, а `check` следит за форматом:
|
||||
|
||||
```markdown
|
||||
- 2026-07-23 `versii-kachestvo-repaki` — Версии и качество одного тайтла.
|
||||
Причина: калибровка болей — не боль, ни разу не возникло за полгода.
|
||||
Была секция: инфра.
|
||||
```
|
||||
|
||||
Реализованные сюда не попадают: у них остаётся коммит и документация. У
|
||||
выкинутой не остаётся ничего — и через квартал она возвращается тем же текстом.
|
||||
Это первое место, куда смотрит дедупликация при заведении.
|
||||
|
||||
Запись не запрещает завести задачу заново: изменился контекст — заводим и
|
||||
ссылаемся на строку, объясняя, что изменилось.
|
||||
|
||||
## Теги
|
||||
|
||||
Единственный механизм разметки, потому что `list --tag` уже умеет отбирать по
|
||||
ним порцию разбора. Отдельных полей мета-строки под это не заводим.
|
||||
|
||||
- `goal:<слаг>` — цель, которой служит задача. Обязателен: задача без цели не
|
||||
попадёт ни в один спринт.
|
||||
- `question` — в файле есть неразобранный раздел «Вопросы».
|
||||
- `sprint:<слаг>` — задача заведена в этом спринте; по нему отбирается первая
|
||||
порция разбора («урожай спринта»). **Ставится сам**: слаг спринта заводит
|
||||
`sprint start` (по умолчанию — дата начала, он же пишется в `SPRINT.md`), и
|
||||
`add` при открытом спринте помечает заводимое. Тег, который надо помнить
|
||||
ставить руками, не ставится никогда — а на нём висит правило «первая порция
|
||||
разбора — урожай прошедшего спринта».
|
||||
- `decomposed` — на цели: разложена на задачи (см. «Файл цели»).
|
||||
|
||||
Отбор — `list --tag a,b`: перечисленные через запятую теги требуются **все
|
||||
сразу** (это И, не ИЛИ). Тег, которого нет ни у одной задачи, `list` называет
|
||||
вслух: молчаливый ноль читается как «таких задач нет», а чаще это опечатка.
|
||||
|
||||
Свои теги проект заводит свободно (партия ревью `review-ГГГГ-ММ-ДД`, тема,
|
||||
источник) — словарь не фиксирован. В индексы теги не выносим: индексы
|
||||
производны, отбор делает `list --tag`, а не глаза.
|
||||
|
||||
## Тест «готова к взятию»
|
||||
|
||||
Задача готова, если из файла отвечаются три вопроса:
|
||||
|
||||
1. **Что станет наблюдаемо иначе**, когда она сделана — снаружи: пользователю,
|
||||
владельцу сервиса или разработчику. «Отрефакторить X» — не ответ; «перестанет
|
||||
ломаться Y при Z» — ответ.
|
||||
2. **По чему видно, что закончено** — критерии приёмки с оракулами.
|
||||
3. **Какой цели она служит** — тег `goal:` и одна строка «почему именно этой».
|
||||
|
||||
Не отвечается первый или второй вопрос → это **идея** (`[idea]`), её место в
|
||||
штурме. Не отвечается третий → либо цель есть и не проставлена, либо задача не
|
||||
служит ничему — тогда её не надо заводить.
|
||||
|
||||
Отвечается всё, но задача не делается одним заходом и не мерджится целиком →
|
||||
**эпик** (`[epic]`), сперва декомпозиция. Эпик временен и исчезает после
|
||||
разбора; цель (`[goal]`) постоянна — не путать.
|
||||
|
||||
Тест применяется при заведении и при переоценке. К старым задачам, которых
|
||||
операция не касается, задним числом не применяется — беклог не переоформляют
|
||||
«заодно».
|
||||
Executable
+2421
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user