Первый прогон агента — по репозиторию, который его же и содержит. Два прохода, 17 находок, все подтверждены по файлам. Пять находок — остатки прежней модели типов в файлах, до которых я не дошёл двумя коммитами раньше. adopt.md держал имена секций роадмапа канона 2 («порядка», «темы») и «пустой goal законен только у идеи»; from-review.md и TODO.md — упразднённый [idea]; task-batch в другом плагине — «задачи-идеи». Правку модели я вёл от документов, которые менял, а не от тех, что на них ссылаются: grep по упразднённому слову дал бы все пять за минуту. Самая дорогая находка оказалась моей и свежей. Таблица типов в canon.md объявляла цель у fix запрещённой, а tasks/SKILL.md и task-fix.md — необязательной; код на стороне вторых. Копия разошлась с домом за один день, обе половины писал один проход. Поправлено не значение, а причина: canon.md дважды объявлял, что фиксирует только словарь типов, — значит колонкам «разделы» и «цель» в нём не место. Осталась таблица из двух колонок и ссылка на дом схемы. Перечень «чем держат проект» пересказывался втроём и разъехался: «метрики и логи» против «мониторинга», «проверки» есть в двух из трёх. При этом tasks/SKILL.md ссылался на дом рядом с собственным пересказом — ссылка не мешает копии разойтись, если копия всё равно стоит. Перечень остался в canon.md, два места ссылаются. README пересказывал раскладку канона блоком кода, и копия была уже неполна — не хватало путей, чьё отсутствие docs.py считает нарушением. Заменено ссылкой. Там же измеренное число из DECISIONS III заменено ссылкой на решение. REMAINING дублировал два отмеченных сделанными пункта TODO и держал счётчики, которые обязан двигать человек: «двенадцати тем и 16 коммитов» (стало 28 и 52), «три неизмеренных изменения» (стало больше). Счётчики отменены как класс, причина записана в шапку. Открытый вопрос про парный статус ADR переформулирован: судья появился, открыт остался охват. Три противоречия вне av-dev-pm: --roadmap-sections перечислен среди флагов init прозой того же файла, объявляющей, что его нет; review-ops берёт журнал docs/review.md и тут же объявляет историю инцидентов принципиально недоступной; «честный предел» конвейера отменял целиком документ docs/research/. Плюс битый якорь ссылки на раздел вычитки. Находка про Co-Authored-By снята как неверная: агент прочитал av-dev-git/skills/commit/SKILL.md как описание практики этого репозитория, а это продукт, уезжающий в чужие проекты. Устав агента не различает «документ про нас» и «документ про то, что мы производим» — остаток записан в REMAINING, в устав пока не дописан. DECISIONS тема 29 (ЧЧШШ–ЮЮЯЯ, следствия 109–113). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
680 lines
63 KiB
Markdown
680 lines
63 KiB
Markdown
---
|
||
name: tasks
|
||
description: Ведение задач и целей как каталога markdown-файлов (одна запись = один файл в items/ + строка в одном из индексов). У каждой записи есть тип (goal, feature, fix, chore, research), и тип решает, каких разделов она требует и что с ней можно делать. Заведение записи из диалога, разбор находок аудита/ревью, декомпозиция на независимо полезные части, штурм сырья, гигиена полей и проверка согласованности индексов. Использовать, когда просят добавить задачу/идею/цель, превратить находки ревью в задачи, разбить задачу, проработать идею, поправить формат или проверить беклог. Ритуал между спринтами — скилл session. Не реализует задачи — этим занимается пайплайн проекта.
|
||
---
|
||
|
||
# Задачи
|
||
|
||
Задачи — каталог markdown-файлов. Одна запись = один файл `items/<slug>.md` плюс
|
||
строка **ровно в одном** индексе. Скилл владеет **форматом и содержимым**:
|
||
заводит, редактирует, закрывает, разбирает находки ревью, дробит, штурмует сырьё.
|
||
|
||
Чем он **не** владеет: ритуалом между спринтами (разбор вопросов → разбор
|
||
прошедшего спринта → переоценка → выбор цели и набор) — это скилл `session`; и
|
||
выполнением задачи — это пайплайн проекта.
|
||
|
||
## Шесть правил, из которых всё следует
|
||
|
||
Ситуация не покрыта инструкцией — решай по ним.
|
||
|
||
0. **Цель — возможность приложения, задача — шаг к ней.** Цель отвечает на «что
|
||
приложение будет уметь», её «Завершение» — наблюдаемый признак того, что уже
|
||
умеет; задача отвечает на «что для этого нужно сделать». Оценивается проект
|
||
по **поведению**, а не по внутреннему устройству, поэтому и роадмап отвечает
|
||
не «сколько работ осталось», а «что уже умеет и чего ещё не умеет».
|
||
Свойство поведения — тоже возможность: «сообщает о своём состоянии»,
|
||
«исход слияния не зависит от порядка доставки» — законные цели.
|
||
1. **Беклог гниёт с той стороны, где его пополняют.** Заведение — самая частая
|
||
операция и с худшим отказом: из одного разговора рождается пять файлов, а
|
||
переоценка потом разгребает то, чего не надо было заводить. Дедупликация и
|
||
фильтр на входе дешевле любой чистки. Заводим только то, что **не делаем
|
||
сейчас** и о потере чего пожалеем.
|
||
2. **Файл — источник истины, индексы производны.** Разошлись — неправы индексы.
|
||
Согласованность механизируема и проверяется командой, а не вниманием: всё,
|
||
что ловит `tasks.py check`, не должно попадать ни в чек-лист, ни в промпт.
|
||
Поэтому **«зачем» живёт в мете файла**, а строка индекса его лишь
|
||
повторяет: пока поле лежало только в индексе, восстановление пропавшей
|
||
строки теряло его молча и навсегда. Единственное исключение намеренное: **в каком
|
||
индексе лежит задача, знают индексы** — «в спринте» это свойство спринта, а
|
||
не файла, поля-состояния нет.
|
||
3. **Причина переживает запись.** Выкинутая без причины задача вернётся через
|
||
квартал тем же текстом. Реализованная оставляет след в коммите — выкинутая не
|
||
оставляет ничего, поэтому у неё есть `REJECTED.md`.
|
||
4. **Порядка нет, есть цель — но цель есть не у всякой задачи.** Приоритетов,
|
||
«повысить» и «встать раньше» нет: «что делать дальше» отвечает набор спринта,
|
||
а между спринтами порядок не нужен никому. Цель обязательна там, где она и
|
||
есть содержание работы, — у **новой возможности** (`feature`). Починка,
|
||
техдолг и разведка служат работоспособности, а не направлению, и живут без
|
||
цели законно; в набор спринта они входят помимо его цели. Придуманная им цель
|
||
— то же враньё, от которого спасает тип.
|
||
|
||
Единственный порядок, который в беклоге всё-таки есть, **производен от типа**,
|
||
а не назначен человеком: **сырьё** (`research` без раздела «Вопрос») стоит в
|
||
конце своей категории. Его не берут, и между берущимся оно каждый раз требует
|
||
открыть файл, чтобы это понять. Раз порядок выводится, его проверяет машина —
|
||
и приоритетом он не становится.
|
||
5. **Тип решает, что с записью можно делать.** Тип — единственная ось и первое
|
||
поле меты: от него зависят обязательные разделы тела, нужна ли цель, берётся
|
||
ли запись в спринт и в каком индексе живёт её строка. Словарь закрыт; ни один
|
||
тип не подошёл — значит, в записи их два, и её надо разделить.
|
||
|
||
## Раскладка
|
||
|
||
Каталог задач — **`docs/tasks`, жёстко**: это часть
|
||
[канона документов](../canon/references/canon.md), и подгоняется под него
|
||
проект, а не наоборот.
|
||
|
||
```
|
||
docs/tasks/
|
||
items/ задачи и цели файлами, <slug>.md, слаги английские
|
||
ROADMAP.md состояние проекта: что уже умеет и чего ещё не умеет
|
||
BACKLOG.md что можно взять — только задачи, целей здесь нет
|
||
SPRINT.md текущий спринт: цель, набор, дата
|
||
REJECTED.md ушедшее БЕЗ реализации, с причиной и датой
|
||
```
|
||
|
||
Правило, снимающее путаницу: **`BACKLOG.md` — то, что берут; `ROADMAP.md` — то,
|
||
подо что берут.** Цель в спринт взять нельзя, поэтому в списке берущихся ей не
|
||
место.
|
||
|
||
**Четыре секции роадмапа, и последняя отвечает на половину вопроса:**
|
||
|
||
| Секция | Англ. | Что в ней |
|
||
| --- | --- | --- |
|
||
| `Запланировано` | `Planned` | очередь значима и обосновывается прозой рядом |
|
||
| `Направления` | `Directions` | очереди нет, тянутся долго |
|
||
| `Сопровождение` | `Operations` | чем держат проект: инструмент, процесс, эксплуатация — не возможности приложения, и потому отдельно |
|
||
| `Готово` | `Done` | достигнутые цели — строкой с датой, **без ссылки на файл**: файл удалён, поведение живёт в спеках |
|
||
|
||
**Порядок тоже канонический, и `Готово` стоит последним не из скромности.**
|
||
Достигнутое **копится**: через год этой секции больше, чем всех остальных
|
||
вместе. Стоя первой, она отодвигает за экран ровно то, ради чего роадмап
|
||
открывают чаще всего, — что делается сейчас и что дальше. Порядок проверяет
|
||
`check`, переставляет `check --fix`.
|
||
|
||
**Секции роадмапа канонические, категории беклога — нет**, и разница не в любви к
|
||
единообразию. У каждой секции роадмапа свой смысл, в достигнутое пишет сам `close`, и
|
||
роадмап, названный по-своему, читался бы только своим автором. Категории беклога
|
||
(`Ядро`, `Инфра`) смысла не несут — это полки домена, и остаются делом проекта.
|
||
Отсюда и разные имена поля меты: у цели **Секция** (часть роадмапа — состояние
|
||
очереди), у задачи **Категория** (полка, в которую она вернётся из спринта).
|
||
|
||
Отсюда четыре правила, которые проверяет `tasks.py check`: **состав закреплён**
|
||
(чужая секция — ошибка, а не вольность), **все четыре обязаны быть** (нет
|
||
секции — нет ответа на её часть вопроса), **язык один на весь индекс**, **порядок
|
||
канонический**. `--roadmap-sections` у `init` нет: выбирать нечего.
|
||
|
||
**Заголовок секции отбит пустой строкой с обеих сторон и написан с прописной.**
|
||
Во всех индексах одинаково, включая категории беклога, которые проект называет сам.
|
||
Написание канонических секций правит `check --fix` (заодно и ссылку на секцию в
|
||
мете файлов: имя секции принадлежит заголовку индекса, файл на неё только
|
||
ссылается); отбивку и порядок он правит везде.
|
||
|
||
Оговорка про `Сопровождение`: слово `окружение` сюда не годится — в
|
||
`architecture.md` оно уже значит боевое окружение приложения, и одно слово в двух
|
||
смыслах развело бы документы канона. А `Разработка`, стоявшая тут раньше,
|
||
называла слишком много: роадмап **весь** про разработку, и секция с таким именем
|
||
не отличалась от остальных ничем.
|
||
|
||
**Секции «блокеры» в беклоге нет.** Блокер — это *состояние* (спринт не может
|
||
продолжаться ни одной задачей), а не полка: он живёт ровно до ответа человека, и
|
||
записи в такой секции не успевают жить. Следы блокера остаются вопросами в
|
||
файлах задач распущенного спринта. Постоянно пустая секция со старой семантикой
|
||
«разбираются пачками» противоречила бы правилу «блокер эскалируется немедленно»,
|
||
поэтому `init` её заводить отказывается, а `check` о ней говорит. **Проекту,
|
||
который переезжает с такой секцией, её надо удалить** — это единственное место,
|
||
где это сказано.
|
||
|
||
**Задача живёт в одном индексе за раз.** Взята в спринт — строка переезжает из
|
||
`BACKLOG.md` в `SPRINT.md`; вышла — обратно. Файл в `items/` при этом **не
|
||
двигается**: он и есть запись, индексы лишь показывают, где она числится.
|
||
|
||
**У сделанной задачи записи не остаётся** — файл и строка удаляются (`close
|
||
--implemented`). Ей хватает коммита и документации проекта; вторая запись была
|
||
бы вторым домом для того же факта. Вопрос «что было в спринте N» отвечается
|
||
даром: `SPRINT.md` лежит под git, `git log -p docs/tasks/SPRINT.md` отдаёт историю
|
||
всех наборов без отдельного журнала.
|
||
|
||
**У достигнутой цели запись остаётся, и это единственное исключение.** Файл
|
||
удаляется так же, а строка переезжает в секцию `Готово` с датой. Причина в том,
|
||
что цель — не работа, а **возможность**: «что приложение умеет» это половина
|
||
вопроса, ради которого роадмап и открывают, и стирать её вместе с файлом значит
|
||
оставить инструмент, отвечающий только «что осталось». Вторым домом это не
|
||
становится: поведение живёт в `openspec/specs/`, а роадмап отвечает **когда и в
|
||
каком порядке оно появилось** — другой вопрос. Ссылки на файл в строке нет
|
||
намеренно: файл удалён, а битая ссылка — законная ошибка `check`.
|
||
|
||
Куда запись может переехать и какой командой — весь набор переходов:
|
||
|
||
```mermaid
|
||
stateDiagram-v2
|
||
state "BACKLOG.md — что берут" as B
|
||
state "ROADMAP.md — подо что берут" as P
|
||
state "SPRINT.md — набор спринта" as S
|
||
state "REJECTED.md — ушла без реализации" as R
|
||
state "записи нет — реализована" as D
|
||
state "ROADMAP.md, «умеет» — цель достигнута" as A
|
||
|
||
[*] --> B: add --type feature|fix|chore|research
|
||
[*] --> P: add --type goal
|
||
B --> P: edit --type goal --section
|
||
P --> B: edit --type feature|fix|chore|research --section
|
||
B --> S: sprint take
|
||
S --> B: sprint drop --reason
|
||
S --> D: close --implemented
|
||
P --> A: close --implemented
|
||
B --> R: close --reason
|
||
S --> R: close --reason
|
||
P --> R: close --reason
|
||
D --> B: reopen --reason
|
||
R --> B: reopen --reason
|
||
A --> P: reopen --reason
|
||
```
|
||
|
||
Состояния здесь — **где числится строка**, а не где лежит файл: файл
|
||
`items/<slug>.md` не двигается ни на одном переходе. Стрелок «руками» на схеме
|
||
нет намеренно — каждый переход это команда, и другого способа его совершить не
|
||
существует.
|
||
|
||
Схема — **сводка**: условия и оговорки живут в тексте разделов, и при
|
||
расхождении прав текст.
|
||
|
||
## Цели
|
||
|
||
**Цель — возможность приложения.** Такой же файл в `items/`, тип `goal` (🎯),
|
||
перечисленный в `ROADMAP.md`. Формулируется ответом на вопрос **«что приложение
|
||
будет уметь»**, а не названием области работ: не «Работа с чтением», а «Чтение
|
||
данных клиентами»; не «Рефакторинг слияния», а «Исход слияния не зависит от
|
||
порядка доставки».
|
||
|
||
**Свойство поведения — тоже возможность.** «Наблюдаемость» это «приложение
|
||
сообщает о своём состоянии»; «прочность слияния» это «исход не зависит от
|
||
порядка». Такие цели законны и переформулировки в функцию не требуют — требуют
|
||
только, чтобы формулировка отвечала на «что приложение делает», а не на «какую
|
||
часть кода мы трогаем».
|
||
|
||
**Что целью не является — работа, которой держат проект.** Состав перечислен
|
||
[в каноне](../canon/references/canon.md), раздел «Сопровождение и эксплуатация»;
|
||
на вопрос «что приложение будет уметь» ничто из него не отвечает. Им отведена отдельная секция роадмапа,
|
||
чтобы они были видны в том же экране и при этом не читались как возможности
|
||
продукта.
|
||
|
||
**Граница проходит по тому, кто наблюдает, а не по теме.** «Приложение сообщает
|
||
о своём состоянии» — возможность: наблюдает пользователь сервиса, и цели место
|
||
среди прочих. «Дежурный видит состояние на одном экране» — сопровождение:
|
||
наблюдаем мы. Одна и та же наблюдаемость попадает в разные секции, и это верно —
|
||
секции отвечают на разные вопросы.
|
||
|
||
**Сопровождение и эксплуатация — целое и часть**, а не синонимы, и та же тема
|
||
живёт ещё в двух местах канона: разделе «Эксплуатация» в `architecture.md` и
|
||
эксплуатационном проходе ревью. Словарь у всех трёх общий и живёт одним домом —
|
||
[canon.md](../canon/references/canon.md), раздел «Сопровождение и эксплуатация».
|
||
Пересказывать его здесь нельзя: три перечня «чем держат проект» уже разъезжались
|
||
на «метриках и логах» против «мониторинга».
|
||
|
||
Секция выбирается так: очередь значима и обоснована прозой — `Запланировано`;
|
||
тянется долго и очереди не имеет — `Направления`; не про приложение, а про то,
|
||
чем его держат, — `Сопровождение`; в `Готово` кладёт сам `close`.
|
||
|
||
- **Список задач цели выводится, а не хранится.** В теле цели — зачем она и что
|
||
считается её завершением; перечня задач там нет. Он был бы третьим индексом и
|
||
поехал бы на первой же закрытой задаче, а `check` про него не знает. Связь
|
||
однонаправленна: задача несёт тег `goal:<слаг>`, перечень даёт
|
||
`tasks.py list --goal <слаг>`.
|
||
- **Статус цели выводится.** Цель достигнута, когда у неё не осталось открытых
|
||
задач; `[x]`/`[~]` руками не ведутся, а `close` цели с живыми задачами
|
||
скрипт запретит. Достижение — `close <цель> --implemented`: файл удаляется,
|
||
строка с датой переезжает в `Готово`. Ошиблись — `reopen` вернёт файл и
|
||
**снимет строку достигнутого**, чтобы роадмап не утверждал того, чего нет. Единственная оговорка: цель без задач неотличима — «ещё не
|
||
разобрана» или «всё закрыто». Различает **тег `decomposed`** в мете
|
||
цели: он ставится, когда цель разложена на задачи. Тег, а не строка в теле —
|
||
потому что проверяется механически: `check` **напоминает** о нём у пустой цели
|
||
(замечанием, не ошибкой — неразобранная цель это законное состояние), а `check
|
||
--fix` сам проставляет его цели, у которой задачи есть.
|
||
- **Тип `[epic]` упразднён.** Он был зонтиком между целью и задачами — «задача,
|
||
которая не мерджится целиком». Зонтик теперь цель, а слишком крупный шаг просто
|
||
дробится на шаги помельче под той же целью, и промежуточному типу места не
|
||
осталось. Замер подтвердил: ноль употреблений на 97 записей двух живых
|
||
проектов. Встретился в чужом беклоге — это цель либо набор задач, и `check`
|
||
назовёт его неизвестным типом.
|
||
|
||
## Тип записи
|
||
|
||
**Тип — единственная ось, и он решает, что с записью можно делать.** Дом типа —
|
||
**поле меты `Тип` первой строкой**; эмодзи в заголовке H1 от него производна, её
|
||
ставит `add` и чинит `check --fix`.
|
||
|
||
| Тип | Обязательные разделы | Цель | В спринт | Устав |
|
||
| --- | --- | --- | --- | --- |
|
||
| 🎯 `goal` | `Завершение` | — | нет | [task-goal.md](references/task-goal.md) |
|
||
| ✨ `feature` | `Затрагивает`, `Критерии приёмки` | **обязательна** | да | [task-feature.md](references/task-feature.md) |
|
||
| 🐞 `fix` | `Воспроизведение`, `Затрагивает`, `Критерии приёмки` | необязательна | да | [task-fix.md](references/task-fix.md) |
|
||
| 🧹 `chore` | `Затрагивает`, `Критерии приёмки` | нет | да | [task-chore.md](references/task-chore.md) |
|
||
| 🔬 `research` | `Вопрос`, `Куда ляжет ответ` | нет | да | [task-research.md](references/task-research.md) |
|
||
|
||
Сверх обязательных у любой задачи допустимы `Рамки` и `Вопросы`. Раздел не из
|
||
схемы своего типа — **замечание, а не ошибка**: свой раздел законная вольность
|
||
проекта, но `Воспроизведение` у `chore` почти всегда значит, что тип проставлен
|
||
не тот, и сказать об этом стоит, не запрещая.
|
||
|
||
**Осей было две, и ортогональность у них была фальшивой.** Тип записи
|
||
(`goal`/`idea`/`task`) и род работы (`kind:<род>` тегом) давали двенадцать клеток
|
||
произведения, из которых законны были шесть: у цели род запрещён, у задачи
|
||
обязателен, у идеи пуст. Плюс алгоритм работы крепится не к `task`, а к `fix` и
|
||
`research` — то есть к роду. Оси схлопнуты, тег `kind:` упразднён.
|
||
|
||
**Тип `idea` упразднён вместе с ними.** Он значил не род работы, а **состояние
|
||
незаполненности** — «первый, второй или третий вопрос теста готовности не
|
||
отвечается», — а состояние типом быть не может: оно меняется по мере того, как
|
||
запись дописывают, а тип меняют командой. Теперь это состояние называется честно:
|
||
`research` без раздела «Вопрос» — **сырьё**. В спринт не берётся ровно как
|
||
прежняя идея, лежит в конце своей категории и отбирается `list --raw`.
|
||
|
||
Словарь **закрыт**. Открытый разъедется на синонимах — `bug`, `bugfix`, `fix`,
|
||
`defect`, — и отбор по типу перестанет отвечать на свой единственный вопрос. Ни
|
||
один тип не подходит — это сигнал, что в задаче их два и её надо разделить.
|
||
|
||
**Требуется тип там, где по нему принимают решение:** `sprint take` без типа
|
||
откажет, потому что не знает, каких разделов требовать. `check` о пропаже только
|
||
**напоминает** — беклог, заведённый до появления типа, законен, и переоформлять
|
||
его «заодно» здесь не просят.
|
||
|
||
**Тип не выбирает профиль ревью и вообще ничего не предписывает пайплайну.**
|
||
Профиль выбирается по факту изменения, а не по типу задачи: `chore` бывает
|
||
миграцией схемы, `fix` — правкой публичного контракта. Правило «предписание
|
||
процесса в теле задачи снимается» типом не отменяется, а подтверждается: он
|
||
описывает работу, а не то, как её проверять.
|
||
|
||
## Как написана задача
|
||
|
||
Три требования к тексту. Первое — про заголовок, два остальных про то, чтобы
|
||
задачу можно было **оценить, не открывая код**.
|
||
|
||
**Заголовок отвечает на вопрос своего типа.** Вопросов три, поэтому и форм три:
|
||
|
||
| Тип | Отвечает на | Пример |
|
||
| --- | --- | --- |
|
||
| 🎯 `goal` | что приложение будет уметь | Соперником может быть компьютер |
|
||
| ✨ `feature`, 🐞 `fix`, 🧹 `chore` | что нужно сделать | Печатать поле одним куском кода |
|
||
| 🔬 `research` | о чём разведка | Подсказка следующего хода |
|
||
|
||
Задача — **глаголом в неопределённой форме**, перед ним допускается «не»: «Не
|
||
отбрасывать молча лишние символы в ходе», а не «Лишние символы молча
|
||
отбрасываются». Описательный заголовок называет **состояние**, а из состояния не
|
||
видно, чего от работы ждут: «Ничья объявляется, пока клетки есть» одинаково
|
||
читается и как жалоба, и как задание, — и в списке, где решают «брать или не
|
||
брать», это разные вещи. `research` формы действия не несёт **намеренно**: её
|
||
исход знание, что делать — ещё неизвестно, и заголовок-действие обещал бы
|
||
решённость, которой нет.
|
||
|
||
Из этого же правила растёт разница индексов: роадмап — список возможностей,
|
||
беклог — список работ, и если заголовки перепутать формами, каждый из них
|
||
начинает читаться как другой.
|
||
|
||
`check` считает заголовки не в форме действия и печатает **число** в блоке
|
||
здоровья, не замечанием на файл: проверка эвристическая (первое слово на
|
||
`-ть`/`-ти`/`-чь`), а беклог, заведённый до правила, не переоформляют «заодно».
|
||
Годность формулировки — не машине: её смотрит
|
||
[агент вычитки](#вычитка-два-прохода-а-не-один).
|
||
|
||
**Функции и границы, а не намерения.** Задача называет, что система начнёт
|
||
делать, и какие границы это трогает: эндпоинт или команду, таблицу и миграцию,
|
||
формат на диске, публичный тип пакета, внешний сервис. Перечень живёт разделом
|
||
«Затрагивает» (форма — [references/task-format.md](references/task-format.md)) и
|
||
требуется к взятию в спринт. Без него задача оценивается по объёму текста, а не
|
||
по объёму поверхности, — и оценка систематически занижена ровно там, где текст
|
||
короткий, а границ много. Названы **границы**, а не то, как они изменятся: план
|
||
реализации живёт в предложении об изменении, а не в задаче.
|
||
|
||
**Предметно, но без усложнения.** Текст задачи читает человек, который решает,
|
||
брать её или нет, и делает это по строке индекса и одному экрану тела.
|
||
|
||
Язык — общий для всех проектных текстов, и живёт он одним файлом:
|
||
[../canon/references/language.md](../canon/references/language.md)
|
||
(информационный стиль, применённый к задачам и документам канона; там же таблицы
|
||
англицизмов и жаргона и то, что из стиля отброшено намеренно). Задаче он даёт
|
||
четыре требования, которые нарушаются чаще прочих:
|
||
|
||
- **глагол вместо отглагольного существительного**: «обработчик не проверяет
|
||
владельца», а не «проверка владельца не осуществляется»;
|
||
- **факт вместо оценки**: «время ответа доходит до 800 мс», а не «работает
|
||
медленно». Оценка без факта рядом — настроение, а не сведение;
|
||
- **англицизм с живым русским аналогом заменяется**: не «зафиксить флоу», а
|
||
«починить порядок доставки». Имя вещи не переводится: слаг, команда, тип в
|
||
коде, `API`;
|
||
- **термин не из документов проекта вводится одной строкой** или не
|
||
употребляется. Свой словарь у задачи — самый дешёвый способ сделать беклог
|
||
нечитаемым для того, кто вернётся к нему через квартал.
|
||
|
||
И одно требование, которое есть только у задачи: **сложность формулировки — не
|
||
признак сложности работы.** Задачу, которую не удаётся сказать просто, чаще
|
||
всего не удаётся и оценить: это либо две задачи, либо сырьё.
|
||
|
||
Эти правила — про **язык**, а не про объём: короткая задача без границ хуже
|
||
длинной с ними.
|
||
|
||
## Инструмент (`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] [--raw] [--index …] [--questions]
|
||
python3 $tk add --dir D --slug S --title T --type goal|feature|fix|chore|research [--section S] [--goal G] [--why «зачем»] [--tag a,b]
|
||
python3 $tk edit S --dir D [--title T] [--why «зачем»] [--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 …] [--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` / `feature` / `fix` / `chore` /
|
||
`research` (как и прочие токены команд), у `add` **обязательное**: без него
|
||
неизвестно, какой шаблон тела класть. Текст задачи при этом русский, а эмодзи в
|
||
заголовке ставит скрипт.
|
||
|
||
**Мутации правят файл и индексы заодно** — руками строку индекса или мету
|
||
не пиши, зови `add`/`edit`/`move`/`close`/`sprint`. Смена заголовка, «зачем», типа,
|
||
цели и **тегов** — это `edit`: он держит H1 (вместе с эмодзи), мету и индекс в
|
||
синхроне. Снятие тега — `--rm-tag` (после ответа на вопрос снимается
|
||
`question`), смена цели — `--goal`, типа — `--type`; оба заменяют прежнее
|
||
значение, а не добавляют второе.
|
||
|
||
**Переезд между индексами — следствие смены типа, а не отдельная команда.**
|
||
`edit <slug> --type goal --section <часть роадмапа>` переносит строку из
|
||
`BACKLOG.md` в `ROADMAP.md` (и обратно — задачным типом плюс
|
||
`--section <категория беклога>`);
|
||
`move` двигает только внутри одного индекса и пишет причину. `--section` у
|
||
`edit` работает **только** при таком переезде — иначе он отсылает к `move`,
|
||
потому что смена секции без причины и есть тот дрейф, который потом никто не
|
||
объяснит. Задача в наборе спринта тип не меняет вовсе: сперва `sprint drop`.
|
||
|
||
Тело задачи скрипт не трогает:
|
||
`add` кладёт заголовок, мета-блок и шаблон с подсказками, тело дописываешь
|
||
редактором (пока плейсхолдер на месте, `check` напоминает).
|
||
|
||
`check` — единственный судья согласованности; что именно он ловит, скажет его
|
||
вывод, здесь не пересказываем. Гоняй его **в начале сессии** и **после каждой
|
||
правки**, даже если правил мутациями: дрейф мог накопиться раньше. Накопившееся
|
||
чини `check --fix` — он детерминированно правит то, где истина однозначна (тип в
|
||
своё поле, эмодзи заголовка, имя поля места, секция, заголовок, дубли, «зачем» из
|
||
индекса в файл, старая форма меты, пометка `decomposed` у цели с задачами, сырьё
|
||
в конец категории), а неоднозначное (задача сразу в двух индексах, нечего
|
||
восстанавливать, **тип, которого неоткуда взять**) печатает отдельной пометкой
|
||
`НЕОДНОЗНАЧНО` — это тебе, и это идёт строкой доклада. **Ссылка на исчезнувший
|
||
файл в пометку не попадает:** `--fix` её просто не трогает, и она остаётся
|
||
`ОШИБКА` обычного `check` — то есть видна, но в докладе её надо назвать отдельно.
|
||
|
||
`--fix` правит **и файлы** — там, где источник ровно один и выбирать не из чего:
|
||
тип переезжает из прежнего дома (тег `kind:`, префикс `[goal]`/`[idea]`) в поле
|
||
меты, заголовок получает эмодзи, поле места — имя по типу, «зачем», оставшееся
|
||
только в индексе, переезжает в мету, цель с задачами получает `decomposed`.
|
||
Каждый случай печатается поимённо.
|
||
|
||
**Тип, который не выводится ниоткуда, `--fix` не угадывает.** `feature` от
|
||
`chore` машина не отличает, и подставленное наугад значение врало бы ровно там,
|
||
где по нему принимают решение. Такие записи идут в `НЕОДНОЗНАЧНО`, и тип им
|
||
проставляет человек — `edit <слаг> --type …`.
|
||
|
||
**Что механизировано, а что нет.** У задачи, взятой в набор (`sprint take` и
|
||
`check` по задачам спринта), проверяется схема её типа, и у каждой части своя
|
||
глубина:
|
||
|
||
- **тип** — жёстко: назван и из закрытого словаря;
|
||
- **критерии приёмки** (`feature`, `fix`, `chore`) — число пунктов жёстко
|
||
(меньше двух отказ, больше пяти замечание), наличие оракула **эвристикой** по
|
||
слову «оракул» в пункте;
|
||
- **прочие разделы схемы** (`Затрагивает`, `Воспроизведение`, `Вопрос`,
|
||
`Куда ляжет ответ`, `Завершение`) — только **наличие непустого**. Содержимое
|
||
машине не видно: границу, которую забыли назвать, она от отсутствующей не
|
||
отличает, а шаги, по которым ничего не воспроизводится, — от годных.
|
||
|
||
Настоящий оракул от слова «оракул» машина тоже не отличает, поэтому эвристика
|
||
даёт только замечание, и в докладе это называется как есть: «проверено наличие
|
||
разделов своего типа и число критериев, годность оракулов и полнота границ —
|
||
глазами».
|
||
|
||
Формат записи, меты, слага, индексов и `REJECTED.md` —
|
||
[references/task-format.md](references/task-format.md); там же тест «готова к
|
||
взятию». Схема и алгоритм каждого типа — по файлу на тип:
|
||
[goal](references/task-goal.md) · [feature](references/task-feature.md) ·
|
||
[fix](references/task-fix.md) · [chore](references/task-chore.md) ·
|
||
[research](references/task-research.md).
|
||
|
||
## Сценарии
|
||
|
||
### Завести запись из диалога
|
||
|
||
1. **Фильтр.** Делаем прямо сейчас — не заводим. Не пожалеем о потере — не
|
||
заводим. Родилось три кандидата — покажи их и спроси, какие заводить: молча
|
||
заведённая пачка и есть тот самый отказ из правила 1.
|
||
2. **Дедуп.** `list` плюс поиск по слагам, полю «зачем» и телам (`grep -ril`),
|
||
**включая `REJECTED.md`**. Нашлось среди живых — **дописываем в существующий
|
||
файл**, а не заводим соседний. Нашлось в `REJECTED.md` — покажи пользователю
|
||
ту строку и что изменилось с момента отказа (`add` предупредит и сам, но
|
||
молча заводить нельзя). Две задачи об одном — самая дорогая находка
|
||
переоценки.
|
||
3. **Тип** — `--type` обязателен, и он же первое содержательное решение:
|
||
|
||
- возможность приложения, а не шаг к ней → `goal`;
|
||
- снаружи появляется то, чего не было → `feature`;
|
||
- поведение расходится с заявленным и **воспроизводится** → `fix`
|
||
(не воспроизводится → `research`);
|
||
- обслуживание, наблюдаемое поведение не меняется → `chore`;
|
||
- исход — знание, а не изменение системы → `research`.
|
||
|
||
Не подходит ни один — в записи их два, разбирай. Не проходит тест готовности
|
||
(см. task-format) — это **сырьё**: `--type research`, раздел «Вопрос» пока
|
||
пуст, место в конце категории. Не делается одним заходом — это не эпик, а
|
||
несколько задач под одной целью: дроби сразу.
|
||
4. **Цель — если тип её требует.** У `feature` должен быть `--goal <слаг>`:
|
||
новая возможность и есть содержание цели. Подходящей нет — либо она
|
||
заводится (`--type goal`), либо перед тобой не `feature`. У `fix`, `chore` и
|
||
`research` цели может не быть вовсе, и придумывать её не надо.
|
||
5. `add …`, затем допиши тело редактором **по схеме своего типа** — шаблон её
|
||
уже разложил, устав типа объясняет каждый раздел. «Зачем» отвечает «зачем
|
||
нужна эта задача» — состояние, остаток, боль, — а не пересказывает первый
|
||
абзац, и пишется **для человека**: не «канонизация внутри транзакции», а
|
||
«тело 40 МиБ держит блокировку 5 секунд, соседние доставки уходят в отказ».
|
||
6. `check`.
|
||
|
||
### Разобрать находки аудита или ревью
|
||
|
||
Ревью и аудиты — тоже источник задач, но с зеркальной диалогу опасностью: не
|
||
пять файлов из одной мысли, а сорок файлов из сорока сырых находок. Защита та
|
||
же, что в самом ревью: кластеризация по причине, дедуп против живых и
|
||
`REJECTED.md`, находка без свидетельства → сырьё (`research`), а не задача, и карта кластеров
|
||
пользователю до создания файлов. Порядок, отображение серьёзности и привязка к
|
||
целям — [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). Обе операции превращают одну запись в
|
||
несколько, и у обеих есть проверяемый тест: части должны **мерджиться порознь** и
|
||
**каждая давать видимую пользу**, а у штурма исход «выкинуть» — полноправный.
|
||
|
||
Там же **шов**: где резать, когда допустимых мест несколько. Коротко — по
|
||
границе, которая одна поднимает ступень ревью выше остальных; и не резать, когда
|
||
обе половины остаются в одной ступени, потому что несокращаемый костяк проверок
|
||
платится за каждую задачу отдельно.
|
||
|
||
### Вычитка: два прохода, а не один
|
||
|
||
Записи судит **не тот агент, который их написал**: самопроверка текста слабее
|
||
всего ровно там, где формулировка казалась удачной при написании. Проходов два,
|
||
и они разные по природе:
|
||
|
||
| Проход | Что смотрит | Над чем работает |
|
||
| --- | --- | --- |
|
||
| `task-form` | заголовок по типу, «зачем» вместо пересказа, границы вместо замысла, годность оракулов, предписание процесса, связь со строкой «Завершения» цели | только `items/`, **открывает файл цели** |
|
||
| `doc-wording` | залог и отглагольные, оценка без факта, стоп-слова, англицизмы, жаргон, неизвестный термин | любой проектный текст, включая документы канона |
|
||
|
||
Разделены они не по охвату, а **по глубине**. Язык проверяется по словам и
|
||
фразам, поштучно; форма записи требует понять, что задача делает, и открыть
|
||
цель, на которую она ссылается. Слитый проход одну половину делает дорогой, а
|
||
вторую — поверхностной. Отсюда и разные модели.
|
||
|
||
Каждый устав отказывается от чужой половины прямо: увиденное не по своей части
|
||
идёт **строкой в границах покрытия**, а не находкой. Две проверки одного места
|
||
расходятся и начинают спорить, и разнимать их потом дороже, чем не сводить.
|
||
|
||
**Порядок — сперва `task-form`.** Его находки меняют решение «брать или не
|
||
брать», а язык — только цену чтения; и переписанный заголовок бессмысленно
|
||
вычитывать до того, как он переписан.
|
||
|
||
Зовутся они **пачкой, а не на каждую запись**: после заведения нескольких задач,
|
||
после разбора находок ревью и на переоценке. Передаётся список файлов и — если
|
||
есть — паспорт, архитектура и конвенции проекта: по ним отличается неизвестный
|
||
термин от известного.
|
||
|
||
Ни один из них ничего не правит. Оба возвращают готовые формулировки, и их
|
||
подставляет скилл: заголовок — `edit <слаг> --title …`, «зачем» —
|
||
`edit <слаг> --why …`, остальное редактором. **Заголовок и «зачем» — это то, по
|
||
чему задачу выбирают, поэтому менять их молча нельзя**: покажи предложенное
|
||
пользователю вместе с тем, что было. Правки в теле (границы, критерии, язык)
|
||
применяются сразу.
|
||
|
||
Всё, что ловит `tasks.py check`, оба не трогают намеренно.
|
||
|
||
### Гигиена полей
|
||
|
||
Правится по ходу любой операции, которая задачи касается (но не «заодно» по
|
||
всему беклогу):
|
||
|
||
- **протухшее «зачем»** — задача изменилась, а поле отвечает на старый вопрос;
|
||
особенно после ответа на вопрос задачи: «Решено: …» на «почему это лежит в
|
||
беклоге» уже не отвечает. Переписывается `edit <slug> --why …` — он правит
|
||
мету файла и строку индекса заодно;
|
||
- **вопрос, застрявший в прозе** — вынимается в раздел «Вопросы» плюс тег
|
||
`question` (`edit --add-tag question`), иначе он не виден ни `list
|
||
--questions`, ни правилу «задача с открытым вопросом в набор не берётся»;
|
||
- **тег, который некому снять** — `question` после ответа снимается `edit
|
||
--rm-tag question` вместе с записью ответа в тело **и опустошением раздела
|
||
«Вопросы»**: судит раздел, а не тег (`references/task-format.md`);
|
||
- **свойство репозитория в рамках** — номер миграции, хеш, версия зависимости:
|
||
в лежалой задаче протухает молча и становится ложной рамкой. Снимается;
|
||
снимок берётся при постановке, а не при заведении;
|
||
- **предписание процесса в теле** — «делать таким-то профилем ревью», «взять
|
||
такой-то агент»: это второй дом для правила выбора и путь понизить требования
|
||
решением, принятым до проектирования. Снимается;
|
||
- **тип, разошедшийся с задачей** — задача заводилась починкой, а после разбора
|
||
оказалось, что поведение никогда и не было заявлено: это `feature`, а не `fix`.
|
||
Правится `edit <slug> --type …`; тип, оставшийся от прошлой формулировки, врёт
|
||
ровно там, где по нему отбирают, **и требует не тех разделов**: у брошенного
|
||
`fix` останется «Воспроизведение», которого нечем заполнить;
|
||
- **сырьё, у которого появился вопрос** — разведка обросла формулировкой, но
|
||
раздел «Вопрос» так и пуст: она числится сырьём и в спринт не берётся.
|
||
Записывается вопрос, и `check --fix` поднимает строку из конца категории;
|
||
- **границы, названные вместо реализации** — «переписать хранилище на новый
|
||
драйвер» в разделе «Затрагивает» это не граница, а замысел. Границы —
|
||
`таблица points и её миграция`, `эндпоинт POST /ingest`, `формат отпечатка на
|
||
диске`. Переписывается перечнем;
|
||
- **англицизм и термин из ниоткуда** — правится по ходу той же операции, что
|
||
касается задачи (см. «Как написана задача»). Именно по ходу: беклог не
|
||
переписывают ради языка.
|
||
|
||
## Переносимость
|
||
|
||
Скилл независим от **языка программирования, сборки, CI и трекера**: он ничего
|
||
не знает ни про Go, ни про npm, ни про конкретный багтрекер — задачи для него
|
||
просто каталог markdown. Текст задач — русский (язык документации проекта);
|
||
зашита только латиница слага. OpenSpec ему тоже не нужен.
|
||
|
||
- **Каталог задач — `docs/tasks`, жёстко**, и `--dir` передаётся явно всегда:
|
||
раскладка канона одинакова во всех проектах, и искать больше нечего. Каталога
|
||
нет — код 3 и вопрос человеку; `init` заводит его **только** когда проект
|
||
действительно новый, а перевод чужой раскладки делает `av-dev-pm:canon`.
|
||
У скрипта поиск вверх по дереву ещё жив — он для непереведённых проектов, и
|
||
полагаться на него скилл не должен: молча найденный чужой каталог это дрейф.
|
||
- **Настройки живут в `docs/.pm.json`**, ключ `tasks`: **имена** файлов и
|
||
заголовков, и только если они отличаются от умолчания. Один конфиг на весь
|
||
канон, а не по одному на каталог. Неизвестный ключ — код 3 на любой команде,
|
||
так что лишнее слово в этом объекте останавливает работу с задачами целиком.
|
||
- **Секции беклога** берутся из заголовков `##` индекса как есть; их количество
|
||
и названия — дело проекта (умолчание `Ядро` / `Инфра`). **В конфиге их нет** —
|
||
второй список разошёлся бы с заголовками молча.
|
||
|
||
### Вызов из другого плагина
|
||
|
||
`$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`. Не решает за пользователя, что важно. Не
|
||
переоформляет существующие задачи «заодно»: правится то, чего касается операция.
|