Files
dev-skills/av-dev-pm/skills/tasks/SKILL.md
T
avandClaude Opus 5 47a2f3de63 язык скиллов av-dev-pm: проход упрощения пятью агентами и разбор находок
Эксперимент: по сабагенту на каждый скилл av-dev-pm, задача — переписать текст
более простыми словами, но только там, где уверен и без потери смысла и
точности. Нормой служил устав языка самого проекта, language.md, включая его
раздел «Порог правки»: правка без нарушенного правила не делается.

36 правок в двенадцати файлах, +64/-63 — почти строго замена, а не
переписывание. Правили залог (пассив с названным деятелем в творительном),
отглагольные существительные, параллельность перечней, канцелярит «является»,
пару garden-path и одно двойное отрицание. Контракт не задет нигде: в диффе нет
изменённых строк-заголовков, а код-спаны встречаются ровно парой минус-плюс,
то есть ни имя, ни флаг, ни путь не переписаны.

Отчёты «что рассматривал и не тронул» вышли длиннее отчётов о правках у всех
пятерых, и это главный результат прогона. Самый частый повод остановиться —
слово, живущее в четырёх файлах: конфляция, интейк, провенанс, непоймание.
Правка в одном месте развела бы словарь, а править все — уже не упрощение
текста скилла, а сквозной проход по репозиторию. Второй повод — формулировка,
дословно повторённая в соседнем плагине: декорреляция, материализация
нерешённого, «при расхождении прав текст».

Шестой агент проверил все 36 правок и нашёл четыре.

Перестановка слов в task-research.md развела формулу с её домом: «число без
источника проход ревью обязан читать как условие» стоит в canon.md и в уставе
doc-consistency, который прямо ссылается на канон как на источник. Откачено —
это ровно тот класс расхождения, который сам doc-consistency и ловит.

«Держит H1, мету и индекс в согласии» — управление требует дополнения, а
language.md в разделе англицизмов прямо оговаривает: русский аналог звучит
коряво — остаётся термин. Взят третий вариант, «согласованными».

В skeletons.md «правка тянет запись, и она называет» — местоимение указывает
на два женских существительных сразу. Стало «и запись называет».

Четвёртая находка не откачена: правка в init/SKILL.md хорошая, но развела
конструкцию с близнецом в canon/SKILL.md — выровнена вторая половина.

Побочно найдена старая логическая инверсия в DECISIONS.md, решение U:
«становится неотличимым, только если отрицание обязательно» — смысл вывернут,
в docs/SKILL.md и во второй записи журнала он правильный. Починено.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 14:49:03 +03:00

680 lines
63 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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`. Не решает за пользователя, что важно. Не
переоформляет существующие задачи «заодно»: правится то, чего касается операция.