готово | запланировано | направления | разработка, англ. done | planned | directions | tooling. Из четырёх предложенных имён отвергнуто одно, и по проверяемой причине: «окружение» уже занято — в architecture.md это боевое окружение приложения, «где работает, что рядом, кто перезапускает», и одно слово в двух смыслах развело бы документы канона. Секции роадмапа стали каноническими, в отличие от секций беклога, и разница выведена, а не назначена: у каждой секции роадмапа своя семантика, в первую пишет сам close, и роадмап, названный по-своему, читался бы только своим автором. Секции беклога — полки, смысла не несут, остаются делом проекта. check проверяет три вещи: состав закреплён (чужая секция — ошибка), все четыре обязаны быть, язык один на весь индекс. Проверено на том случае, ради которого правило и заводилось: «Что уже пройдено», которую healthlog вёл руками, теперь называется ошибкой поимённо. Оба языка прогнаны вживую, включая close в английский роадмап. Ключа tasks.achieved_section не появилось — секция достигнутого опознаётся по каноническому имени в любом из языков; --roadmap-sections у init упразднён, выбирать больше нечего. Названные вслух компромиссы: «готово» слегка тянет в трекерную рамку «состояние работы», тогда как секция про возможность — перевесила читаемость; цель в «запланировано» может быть уже наполовину построена, это очередь, а не «не начато», «в работе» живёт в SPRINT.md. DECISIONS 19: ГГГ переписан, добавлен ДДД, следствие 78 заменено. TODO 7 закрыт. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
539 lines
49 KiB
Markdown
539 lines
49 KiB
Markdown
---
|
||
name: tasks
|
||
description: Ведение задач и целей как каталога markdown-файлов (одна запись = один файл в items/ + строка в одном из индексов). Заведение задачи, идеи или цели из диалога, разбор находок аудита/ревью, декомпозиция на независимо полезные части, мозговой штурм идеи, гигиена полей и проверка согласованности индексов. Использовать, когда просят добавить задачу/идею/цель, превратить находки ревью в задачи, разбить задачу, проработать идею, поправить формат или проверить беклог. Ритуал между спринтами — скилл session. Не реализует задачи — этим занимается пайплайн проекта.
|
||
---
|
||
|
||
# Задачи
|
||
|
||
Задачи — каталог markdown-файлов. Одна запись = один файл `items/<slug>.md` плюс
|
||
строка **ровно в одном** индексе. Скилл владеет **форматом и содержимым**:
|
||
заводит, редактирует, закрывает, разбирает находки ревью, дробит, штурмует идеи.
|
||
|
||
Чем он **не** владеет: ритуалом между спринтами (разбор вопросов → разбор
|
||
прошедшего спринта → переоценка → выбор цели и набор) — это скилл `session`; и
|
||
выполнением задачи — это пайплайн проекта.
|
||
|
||
## Пять правил, из которых всё следует
|
||
|
||
Ситуация не покрыта инструкцией — решай по ним.
|
||
|
||
0. **Цель — возможность приложения, задача — шаг к ней.** Цель отвечает на «что
|
||
приложение будет уметь», её «Завершение» — наблюдаемый признак того, что уже
|
||
умеет; задача отвечает на «что для этого нужно сделать». Оценивается проект
|
||
по **поведению**, а не по внутреннему устройству, поэтому и роадмап отвечает
|
||
не «сколько работ осталось», а «что уже умеет и чего ещё не умеет».
|
||
Свойство поведения — тоже возможность: «сообщает о своём состоянии»,
|
||
«исход слияния не зависит от порядка доставки» — законные цели.
|
||
1. **Беклог гниёт с той стороны, где его пополняют.** Заведение — самая частая
|
||
операция и с худшим отказом: из одного разговора рождается пять файлов, а
|
||
переоценка потом разгребает то, чего не надо было заводить. Дедупликация и
|
||
фильтр на входе дешевле любой чистки. Заводим только то, что **не делаем
|
||
сейчас** и о потере чего пожалеем.
|
||
2. **Файл — источник истины, индексы производны.** Разошлись — неправы индексы.
|
||
Согласованность механизируема и проверяется командой, а не вниманием: всё,
|
||
что ловит `tasks.py check`, не должно попадать ни в чек-лист, ни в промпт.
|
||
Поэтому **«зачем» живёт в мете файла**, а строка индекса его лишь
|
||
повторяет: пока поле лежало только в индексе, восстановление пропавшей
|
||
строки теряло его молча и навсегда. Единственное исключение намеренное: **в каком
|
||
индексе лежит задача, знают индексы** — «в спринте» это свойство спринта, а
|
||
не файла, поля-состояния нет.
|
||
3. **Причина переживает запись.** Выкинутая без причины задача вернётся через
|
||
квартал тем же текстом. Реализованная оставляет след в коммите — выкинутая не
|
||
оставляет ничего, поэтому у неё есть `REJECTED.md`.
|
||
4. **Порядка нет, есть цель — но цель есть не у всякой задачи.** Приоритетов,
|
||
«повысить» и «встать раньше» нет: «что делать дальше» отвечает набор спринта,
|
||
а между спринтами порядок не нужен никому. Цель обязательна там, где она и
|
||
есть содержание работы, — у **новой возможности** (`kind:feature`). Починка,
|
||
техдолг и разведка служат работоспособности, а не направлению, и живут без
|
||
цели законно; в набор спринта они входят помимо его цели. Придуманная им цель
|
||
— то же враньё, от которого спасает род работы.
|
||
|
||
## Раскладка
|
||
|
||
Каталог задач — **`docs/tasks`, жёстко**: это часть
|
||
[канона документов](../canon/references/canon.md), и подгоняется под него
|
||
проект, а не наоборот.
|
||
|
||
```
|
||
docs/tasks/
|
||
items/ задачи и цели файлами, <slug>.md, слаги английские
|
||
ROADMAP.md состояние проекта: что уже умеет и чего ещё не умеет
|
||
BACKLOG.md что можно взять — только задачи, целей здесь нет
|
||
SPRINT.md текущий спринт: цель, набор, дата
|
||
REJECTED.md ушедшее БЕЗ реализации, с причиной и датой
|
||
```
|
||
|
||
Правило, снимающее путаницу: **`BACKLOG.md` — то, что берут; `ROADMAP.md` — то,
|
||
подо что берут.** Цель в спринт взять нельзя, поэтому в списке берущихся ей не
|
||
место.
|
||
|
||
**Четыре секции роадмапа, и первая отвечает на половину вопроса:**
|
||
|
||
| Секция | Англ. | Что в ней |
|
||
| --- | --- | --- |
|
||
| `готово` | `done` | достигнутые цели — строкой с датой, **без ссылки на файл**: файл удалён, поведение живёт в спеках |
|
||
| `запланировано` | `planned` | очередь значима и обосновывается прозой рядом |
|
||
| `направления` | `directions` | очереди нет, тянутся долго |
|
||
| `разработка` | `tooling` | инструмент и процесс — не возможности приложения, и потому отдельно |
|
||
|
||
**Секции роадмапа канонические, секции беклога — нет**, и разница не в любви к
|
||
единообразию. У каждой секции роадмапа свой смысл, в первую пишет сам `close`, и
|
||
роадмап, названный по-своему, читался бы только своим автором. Секции беклога
|
||
(`ядро`, `инфра`) смысла не несут — это полки, и остаются делом проекта.
|
||
|
||
Отсюда три правила, которые проверяет `tasks.py check`: **состав закреплён**
|
||
(чужая секция — ошибка, а не вольность), **все четыре обязаны быть** (нет
|
||
секции — нет ответа на её часть вопроса), **язык один на весь индекс**.
|
||
`--roadmap-sections` у `init` нет: выбирать нечего.
|
||
|
||
Оговорка про `разработка`: слово `окружение` сюда не годится — в
|
||
`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
|
||
[*] --> P: add --type goal
|
||
B --> P: edit --type goal --section
|
||
P --> B: edit --type task --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`. Формулируется ответом на вопрос **«что приложение
|
||
будет уметь»**, а не названием области работ: не «Работа с чтением», а «Чтение
|
||
данных клиентами»; не «Рефакторинг слияния», а «Исход слияния не зависит от
|
||
порядка доставки».
|
||
|
||
**Свойство поведения — тоже возможность.** «Наблюдаемость» это «приложение
|
||
сообщает о своём состоянии»; «прочность слияния» это «исход не зависит от
|
||
порядка». Такие цели законны и переформулировки в функцию не требуют — требуют
|
||
только, чтобы формулировка отвечала на «что приложение делает», а не на «какую
|
||
часть кода мы трогаем».
|
||
|
||
**Что целью не является — работа над станком.** Инструмент, процесс, сборка,
|
||
сам этот скилл: на вопрос «что приложение будет уметь» они не отвечают. Им
|
||
отведена отдельная секция роадмапа, чтобы они были видны в том же экране и при
|
||
этом не читались как возможности продукта.
|
||
|
||
Секция выбирается так: очередь значима и обоснована прозой — `запланировано`;
|
||
тянется долго и очереди не имеет — `направления`; не про приложение —
|
||
`разработка`; в `готово` кладёт сам `close`.
|
||
|
||
- **Список задач цели выводится, а не хранится.** В теле цели — зачем она и что
|
||
считается её завершением; перечня задач там нет. Он был бы третьим индексом и
|
||
поехал бы на первой же закрытой задаче, а `check` про него не знает. Связь
|
||
однонаправленна: задача несёт тег `goal:<слаг>`, перечень даёт
|
||
`tasks.py list --goal <слаг>`.
|
||
- **Статус цели выводится.** Цель достигнута, когда у неё не осталось открытых
|
||
задач; `[x]`/`[~]` руками не ведутся, а `close` цели с живыми задачами
|
||
скрипт запретит. Достижение — `close <цель> --implemented`: файл удаляется,
|
||
строка с датой переезжает в `готово`. Ошиблись — `reopen` вернёт файл и
|
||
**снимет строку достигнутого**, чтобы роадмап не утверждал того, чего нет. Единственная оговорка: цель без задач неотличима — «ещё не
|
||
разобрана» или «всё закрыто». Различает **тег `decomposed`** в мете
|
||
цели: он ставится, когда цель разложена на задачи. Тег, а не строка в теле —
|
||
потому что проверяется механически: `check` **напоминает** о нём у пустой цели
|
||
(замечанием, не ошибкой — неразобранная цель это законное состояние), а `check
|
||
--fix` сам проставляет его цели, у которой задачи есть.
|
||
- **Тип `[epic]` упразднён.** Он был зонтиком между целью и задачами — «задача,
|
||
которая не мерджится целиком». Зонтик теперь цель, а слишком крупный шаг просто
|
||
дробится на шаги помельче под той же целью, и промежуточному типу места не
|
||
осталось. Замер подтвердил: ноль употреблений на 97 записей двух живых
|
||
проектов. Встретился в чужом беклоге — это цель либо набор задач, и `check`
|
||
назовёт его неизвестным типом.
|
||
|
||
## Род работы
|
||
|
||
**Тип записи и род работы — две оси, и путать их нельзя.** Тип отвечает «что это
|
||
за запись» (цель, идея, задача), род — «какого рода работа»: `feature`, `fix`,
|
||
`chore`, `research`. Одним значением на оба вопроса не ответить: идея бывает
|
||
*про* функцию, а цель функцией *и является*.
|
||
|
||
- **`feature`** — снаружи появляется или меняется то, чего раньше не было.
|
||
- **`fix`** — поведение расходится с заявленным, и расхождение воспроизводится.
|
||
Не воспроизводится — это `research`, а не `fix`.
|
||
- **`chore`** — обслуживание: зависимости, сборка, перенос, чистка. Наблюдаемое
|
||
поведение не меняется, и в этом всё дело: **у `chore` тест готовности слабее
|
||
честно**, а не молча. «Что станет наблюдаемо иначе» здесь отвечается
|
||
разработчику («перестанет собираться два раза», «уедет последний вызов
|
||
устаревшего API»), а не пользователю. Пока рода не было, такие задачи либо не
|
||
заводились, либо формулировались как выдуманная польза.
|
||
- **`research`** — исход работы знание, а не изменение системы: ответ на вопрос,
|
||
замер, разведка. Приёмка — записанный ответ (`docs/research/`, ADR, тело
|
||
задачи), а не изменённый код.
|
||
|
||
Дом рода — **тег `kind:<род>`**, а не префикс заголовка и не поле меты: теги
|
||
здесь единственный механизм разметки, и `list --kind fix` работает даром. Цена
|
||
известна: в строку индекса род не попадает (индексы производны), и «в наборе одни
|
||
починки» видно командой, а не глазами по `SPRINT.md`.
|
||
|
||
Словарь **закрыт**. Открытый разъедется на синонимах — `bug`, `bugfix`, `fix`,
|
||
`defect`, — и отбор по роду перестанет отвечать на свой единственный вопрос. Ни
|
||
один род не подходит — это сигнал, что в задаче их два и её надо разделить.
|
||
|
||
**Род обязателен у задачи, у цели запрещён, у идеи необязателен** — идея получает
|
||
его, когда становится задачей. Требуется он там, где по нему принимают решение:
|
||
`sprint take` без рода откажет. `check` о пропаже только **напоминает** — беклог,
|
||
заведённый до появления рода, законен, и переоформлять его «заодно» здесь не
|
||
просят.
|
||
|
||
**Род решает и то, обязательна ли цель.** `feature` без цели не бывает: новая
|
||
возможность и есть содержание цели, и если подходящей нет — либо она заводится,
|
||
либо это не `feature`. `fix`, `chore` и `research` живут без цели законно, и
|
||
`check` о них молчит: они служат работоспособности, а не направлению. Это
|
||
единственный случай, когда род что-то определяет за пределами отбора, — и
|
||
определяет он учёт, а не процесс проверки.
|
||
|
||
**Род не выбирает профиль ревью и вообще ничего не предписывает пайплайну.**
|
||
Профиль выбирается по факту изменения, а не по роду задачи: `chore` бывает
|
||
миграцией схемы, `fix` — правкой публичного контракта. Правило «предписание
|
||
процесса в теле задачи снимается» родом не отменяется, а подтверждается: он
|
||
описывает работу, а не то, как её проверять.
|
||
|
||
## Как написана задача
|
||
|
||
Два требования к тексту, и оба про то, чтобы задачу можно было **оценить, не
|
||
открывая код**.
|
||
|
||
**Функции и границы, а не намерения.** Задача называет, что система начнёт
|
||
делать, и какие границы это трогает: эндпоинт или команду, таблицу и миграцию,
|
||
формат на диске, публичный тип пакета, внешний сервис. Перечень живёт разделом
|
||
«Затрагивает» (форма — [references/task-format.md](references/task-format.md)) и
|
||
требуется к взятию в спринт. Без него задача оценивается по объёму текста, а не
|
||
по объёму поверхности, — и оценка систематически занижена ровно там, где текст
|
||
короткий, а границ много. Названы **границы**, а не то, как они изменятся: план
|
||
реализации живёт в предложении об изменении, а не в задаче.
|
||
|
||
**Предметно, но без усложнения.** Текст задачи читает человек, который решает,
|
||
брать её или нет, и делает это по строке индекса и одному экрану тела.
|
||
|
||
- **англицизм, у которого есть русское слово, — заменяется**: не «зафиксить
|
||
флоу», а «починить порядок доставки»; не «отрефакторить», а «убрать второй
|
||
путь приёма». Английские остаются там, где они и есть имя вещи: слаг,
|
||
`capability`, имя пакета, команда, тип в коде.
|
||
- **термин, которого нет в паспорте, архитектуре или конвенциях проекта, вводится
|
||
одной строкой** или не употребляется. Свой словарь у задачи — самый дешёвый
|
||
способ сделать беклог нечитаемым для того, кто вернётся к нему через квартал.
|
||
- **сложность формулировки — не признак сложности работы.** Задачу, которую не
|
||
удаётся сказать просто, чаще всего не удаётся и оценить: это либо две задачи,
|
||
либо идея.
|
||
|
||
Эти правила — про **язык**, а не про объём: короткая задача без границ хуже
|
||
длинной с ними.
|
||
|
||
## Инструмент (`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] [--kind K] [--tag a,b] [--goal S] [--index …] [--questions]
|
||
python3 $tk add --dir D --slug S --title T [--type goal|idea] [--section S] [--goal G] [--kind K] [--why «зачем»] [--tag a,b]
|
||
python3 $tk edit S --dir D [--title T] [--why «зачем»] [--type T] [--goal G] [--kind K] [--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 …] [--roadmap-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` / `task` (как и прочие токены
|
||
команд); `task` префикса не несёт, остальные кодируются `[goal]`/`[idea]` в
|
||
заголовке. Текст задачи при этом русский.
|
||
|
||
**Мутации правят файл и индексы заодно** — руками строку индекса или мету
|
||
не пиши, зови `add`/`edit`/`move`/`close`/`sprint`. Смена заголовка, «зачем», типа,
|
||
цели, рода работы и **тегов** — это `edit`: он держит H1, мету и индекс в синхроне.
|
||
Снятие тега — `--rm-tag` (после ответа на вопрос снимается `question`), смена
|
||
цели — `--goal`, рода — `--kind`; оба заменяют прежнее значение, а не добавляют
|
||
второе.
|
||
|
||
**Переезд между индексами — следствие смены типа, а не отдельная команда.**
|
||
`edit <slug> --type goal --section <часть роадмапа>` переносит строку из
|
||
`BACKLOG.md` в `ROADMAP.md` (и обратно `--type task --section <секция беклога>`);
|
||
`move` двигает только внутри одного индекса и пишет причину. `--section` у
|
||
`edit` работает **только** при таком переезде — иначе он отсылает к `move`,
|
||
потому что смена секции без причины и есть тот дрейф, который потом никто не
|
||
объяснит. Задача в наборе спринта тип не меняет вовсе: сперва `sprint drop`.
|
||
|
||
Тело задачи скрипт не трогает:
|
||
`add` кладёт заголовок, мета-блок и шаблон с подсказками, тело дописываешь
|
||
редактором (пока плейсхолдер на месте, `check` напоминает).
|
||
|
||
`check` — единственный судья согласованности; что именно он ловит, скажет его
|
||
вывод, здесь не пересказываем. Гоняй его **в начале сессии** и **после каждой
|
||
правки**, даже если правил мутациями: дрейф мог накопиться раньше. Накопившееся
|
||
чини `check --fix` — он детерминированно правит то, где истина однозначна
|
||
(секция, заголовок, дубли, «зачем» из индекса в файл, старая форма меты,
|
||
пометка `decomposed` у цели с задачами), а неоднозначное (задача сразу в двух
|
||
индексах, нечего восстанавливать) печатает отдельной пометкой `НЕОДНОЗНАЧНО` —
|
||
это тебе, и это идёт строкой доклада. **Ссылка на исчезнувший файл в пометку не
|
||
попадает:** `--fix` её просто не трогает, и она остаётся `ОШИБКА` обычного
|
||
`check` — то есть видна, но в докладе её надо назвать отдельно.
|
||
|
||
`--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 goal`).
|
||
4. **Цель задачи — если род её требует.** У `feature` должен быть
|
||
`--goal <слаг>`: новая возможность и есть содержание цели. Подходящей нет —
|
||
либо она заводится (`--type goal`), либо перед тобой не `feature`. У `fix`,
|
||
`chore` и `research` цели может не быть вовсе, и придумывать её не надо. У
|
||
идеи цель проставляется, когда идея становится задачей.
|
||
5. **Род работы** — `--kind feature|fix|chore|research` (см. «Род работы»). Не
|
||
подходит ни один — задача не одна, разбирай.
|
||
6. `add …`, затем допиши тело редактором: одна фраза, **затрагиваемые границы**,
|
||
критерии приёмки с оракулами, рамки. «Зачем» отвечает «зачем нужна эта
|
||
задача» — состояние, остаток, боль, — а не пересказывает первый абзац, и
|
||
пишется **для человека**: не «канонизация внутри транзакции», а «тело 40 МиБ
|
||
держит блокировку 5 секунд, соседние доставки уходят в отказ».
|
||
7. `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> --why …` — он правит
|
||
мету файла и строку индекса заодно;
|
||
- **вопрос, застрявший в прозе** — вынимается в раздел «Вопросы» плюс тег
|
||
`question` (`edit --add-tag question`), иначе он не виден ни `list
|
||
--questions`, ни правилу «задача с открытым вопросом в набор не берётся»;
|
||
- **тег, который некому снять** — `question` после ответа снимается `edit
|
||
--rm-tag question` вместе с записью ответа в тело **и опустошением раздела
|
||
«Вопросы»**: судит раздел, а не тег (`references/task-format.md`);
|
||
- **свойство репозитория в рамках** — номер миграции, хеш, версия зависимости:
|
||
в лежалой задаче протухает молча и становится ложной рамкой. Снимается;
|
||
снимок берётся при постановке, а не при заведении;
|
||
- **предписание процесса в теле** — «делать таким-то профилем ревью», «взять
|
||
такой-то агент»: это второй дом для правила выбора и путь понизить требования
|
||
решением, принятым до проектирования. Снимается;
|
||
- **род, разошедшийся с задачей** — задача заводилась починкой, а после разбора
|
||
оказалось, что поведение никогда и не было заявлено: это `feature`, а не `fix`.
|
||
Правится `edit <slug> --kind …`; род, оставшийся от прошлой формулировки, врёт
|
||
ровно там, где по нему отбирают;
|
||
- **границы, названные вместо реализации** — «переписать хранилище на новый
|
||
драйвер» в разделе «Затрагивает» это не граница, а замысел. Границы —
|
||
`таблица 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`. Не решает за пользователя, что важно. Не
|
||
переоформляет существующие задачи «заодно»: правится то, чего касается операция.
|