av-dev-tasks: починены находки ревью, добавлена адаптация чужого репозитория
- атомарность: sprint drop и move собирают план правок целиком и пишут одним проходом; раньше отказ на втором слаге оставлял первый файл переписанным при нетронутом индексе - хук переехал в мета-строку файла: индекс стал производным, и check --fix больше не теряет текст, восстанавливая строку - механизировано то, что было записано, но не проверялось: слаг спринта и автотег, отказ по факту непустого раздела вопросов, число критериев, пометка decomposed, покрытие причин - reopen возвращает закрытую задачу: без него порядок «пайплайн доложил → приёмщик судит → владелец закрывает» был односторонним - скилл adopt: приходит в чужой репозиторий и выводит заполненный каталог задач. На копии беклога healthlog — 12 целей, 38 задач, 36 переименований, 86 ссылок в 36 файлах, check зелёный
This commit is contained in:
@@ -25,8 +25,11 @@ description: Ведение задач и целей как каталога mar
|
||||
2. **Файл — источник истины, индексы производны.** Разошлись — неправы индексы.
|
||||
Согласованность механизируема и проверяется командой, а не вниманием: всё,
|
||||
что ловит `tasks.py check`, не должно попадать ни в чек-лист, ни в промпт.
|
||||
Единственное исключение намеренное: **в каком индексе лежит задача, знают
|
||||
индексы** — «в спринте» это свойство спринта, а не файла, поля-состояния нет.
|
||||
Поэтому **хук живёт в мета-строке файла**, а строка индекса его лишь
|
||||
повторяет: пока хук лежал только в индексе, восстановление пропавшей строки
|
||||
теряло его молча и навсегда. Единственное исключение намеренное: **в каком
|
||||
индексе лежит задача, знают индексы** — «в спринте» это свойство спринта, а
|
||||
не файла, поля-состояния нет.
|
||||
3. **Причина переживает запись.** Выкинутая без причины задача вернётся через
|
||||
квартал тем же текстом. Реализованная оставляет след в коммите — выкинутая не
|
||||
оставляет ничего, поэтому у неё есть `REJECTED.md`.
|
||||
@@ -51,6 +54,15 @@ description: Ведение задач и целей как каталога mar
|
||||
подо что берут.** Цель в спринт взять нельзя, поэтому в списке берущихся ей не
|
||||
место.
|
||||
|
||||
**Секции «блокеры» в беклоге нет.** Блокер — это *состояние* (спринт не может
|
||||
продолжаться ни одной задачей), а не полка: он живёт ровно до ответа человека, и
|
||||
записи в такой секции не успевают жить. Следы блокера остаются вопросами в
|
||||
файлах задач распущенного спринта. Постоянно пустая секция со старой семантикой
|
||||
«разбираются пачками» противоречила бы правилу «блокер эскалируется немедленно»,
|
||||
поэтому `init` её заводить отказывается, а `check` о ней говорит. **Проекту,
|
||||
который переезжает с такой секцией, её надо удалить** — это единственное место,
|
||||
где это сказано.
|
||||
|
||||
**Задача живёт в одном индексе за раз.** Взята в спринт — строка переезжает из
|
||||
`BACKLOG.md` в `SPRINT.md`; вышла — обратно. Файл в `items/` при этом **не
|
||||
двигается**: он и есть запись, индексы лишь показывают, где она числится.
|
||||
@@ -77,8 +89,10 @@ description: Ведение задач и целей как каталога mar
|
||||
- **Статус цели выводится.** Цель закрыта, когда у неё не осталось открытых
|
||||
задач; `[x]`/`[~]` руками не ведутся, а `close` цели с живыми задачами
|
||||
скрипт запретит. Единственная оговорка: цель без задач неотличима — «ещё не
|
||||
декомпозирована» или «всё закрыто». Различает пометка «декомпозирована» в
|
||||
теле, проставляемая при переоценке.
|
||||
разобрана» или «всё закрыто». Различает **тег `decomposed`** в мета-строке
|
||||
цели: он ставится, когда цель разложена на задачи. Тег, а не строка в теле —
|
||||
потому что проверяется механически: `check` требует его у пустой цели, а
|
||||
`check --fix` сам проставляет его цели, у которой задачи есть.
|
||||
- **`[goal]` и `[epic]` — разные вещи.** Цель **постоянна**: живёт, пока живёт
|
||||
направление. Эпик **временен**: это задача, которая не мерджится целиком, её
|
||||
разбирают, и он исчезает. Два срока жизни под одним словом разъезжаются,
|
||||
@@ -86,21 +100,38 @@ description: Ведение задач и целей как каталога mar
|
||||
|
||||
## Инструмент (`tasks.py`)
|
||||
|
||||
Пусть `tk="$CLAUDE_PLUGIN_ROOT/skills/tasks/scripts/tasks.py"`.
|
||||
Пусть `tk="$CLAUDE_PLUGIN_ROOT/skills/tasks/scripts/tasks.py"`, а `D` — каталог
|
||||
задач проекта (см. «Переносимость»; `--dir` опускается только если каталог
|
||||
лежит в умолчаниях под текущим каталогом).
|
||||
|
||||
```
|
||||
python3 $tk check # согласованность всех индексов + здоровье, exit 1 при расхождениях
|
||||
python3 $tk check --fix # + починить безопасный дрейф (секция, заголовок, дубли)
|
||||
python3 $tk list [--stale] [--section S] [--type T] [--tag T] [--goal S] [--index …] [--questions]
|
||||
python3 $tk add --slug S --title T [--type goal|idea|epic] [--section S] [--goal G] [--hook H] [--tag a,b]
|
||||
python3 $tk edit S [--title T] [--hook H] [--type T] [--goal G] [--add-tag a,b] [--rm-tag c]
|
||||
python3 $tk move S --section S [--reason R] [--after S | --first]
|
||||
python3 $tk close S --reason R # в REJECTED.md + удалить (ушла без реализации)
|
||||
python3 $tk close S --implemented # просто удалить (реализована, есть коммит)
|
||||
python3 $tk sprint start --goal S | take S… | drop S… --reason R | close [--dissolve --reason R]
|
||||
python3 $tk init [--dir D] [--sections …] [--plan-sections …] [--items …] [--backlog …] …
|
||||
python3 $tk check --dir D # согласованность индексов + здоровье
|
||||
python3 $tk check --dir D --fix # + починить дрейф (секция, заголовок, дубли, хук, дом)
|
||||
python3 $tk list --dir D [--stale] [--section S] [--type T] [--tag a,b] [--goal S] [--index …] [--questions]
|
||||
python3 $tk add --dir D --slug S --title T [--type goal|idea|epic] [--section S] [--goal G] [--hook H] [--tag a,b]
|
||||
python3 $tk edit S --dir D [--title T] [--hook H] [--type T] [--goal G] [--add-tag a,b] [--rm-tag c]
|
||||
python3 $tk move S --dir D --section S [--reason R] [--after S | --first]
|
||||
python3 $tk close S --dir D --reason R # в REJECTED.md + удалить (ушла без реализации)
|
||||
python3 $tk close S --dir D --implemented # просто удалить (реализована, приёмка сошлась)
|
||||
python3 $tk reopen S --dir D --reason R # вернуть закрытую: приёмка не сошлась
|
||||
python3 $tk sprint start --goal S --dir D | take S… | drop S… --reason R | close [--dissolve --reason R]
|
||||
python3 $tk init --dir D [--sections …] [--plan-sections …] [--items …] [--backlog …] …
|
||||
python3 $tk adopt scan --from … | apply --plan … # разовая адаптация чужого репозитория, скилл adopt
|
||||
```
|
||||
|
||||
**Коды выхода — единый словарь; на нём ветвятся скиллы, а не на тексте вывода:**
|
||||
|
||||
| Код | Что случилось | Что делать |
|
||||
| --- | --- | --- |
|
||||
| 0 | сошлось / сделано | дальше по сценарию |
|
||||
| 1 | **только `check`:** найден дрейф индексов и файлов | `check --fix`, остаток разобрать |
|
||||
| 2 | ошибка употребления: аргументы или нарушенное правило | читать сообщение, это отказ по существу |
|
||||
| 3 | окружение: каталог не найден, конфиг битый или мимо диска | чинится путём или `.tasks.json`, повтор не поможет |
|
||||
| 4 | внутренний сбой | дефект скрипта, доложить |
|
||||
|
||||
Различать 1 и 3 обязательно: «дрейф в беклоге» — рабочая ситуация, «каталога
|
||||
нет» — нерабочая, и одинаковая реакция на них была бы неверна в обоих случаях.
|
||||
|
||||
Тип — английское ключевое слово `goal` / `idea` / `epic` / `task` (как и прочие
|
||||
токены команд); `task` префикса не несёт, остальные кодируются `[goal]`/
|
||||
`[idea]`/`[epic]` в заголовке. Текст задачи при этом русский.
|
||||
@@ -109,16 +140,40 @@ python3 $tk init [--dir D] [--sections …] [--plan-sections …] [--items …]
|
||||
не пиши, зови `add`/`edit`/`move`/`close`/`sprint`. Смена заголовка, хука, типа,
|
||||
цели и **тегов** — это `edit`: он держит H1, мета-строку и индекс в синхроне.
|
||||
Снятие тега — `--rm-tag` (после ответа на вопрос снимается `question`), смена
|
||||
цели — `--goal`, он заменяет прежний `goal:*`. Тело задачи скрипт не трогает:
|
||||
цели — `--goal`, он заменяет прежний `goal:*`.
|
||||
|
||||
**Переезд между индексами — следствие смены типа, а не отдельная команда.**
|
||||
`edit <slug> --type goal --section <часть плана>` переносит строку из
|
||||
`BACKLOG.md` в `PLAN.md` (и обратно `--type task --section <секция беклога>`);
|
||||
`move` двигает только внутри одного индекса и пишет причину. `--section` у
|
||||
`edit` работает **только** при таком переезде — иначе он отсылает к `move`,
|
||||
потому что смена секции без причины и есть тот дрейф, который потом никто не
|
||||
объяснит. Задача в наборе спринта тип не меняет вовсе: сперва `sprint drop`.
|
||||
|
||||
Тело задачи скрипт не трогает:
|
||||
`add` кладёт заголовок, мета-строку и шаблон с подсказками, тело дописываешь
|
||||
редактором (пока плейсхолдер на месте, `check` напоминает).
|
||||
|
||||
`check` — единственный судья согласованности; что именно он ловит, скажет его
|
||||
вывод, здесь не пересказываем. Гоняй его **в начале сессии** и **после каждой
|
||||
правки**, даже если правил мутациями: дрейф мог накопиться раньше. Накопившееся
|
||||
чини `check --fix` — он детерминированно правит безопасное, а неоднозначное
|
||||
(ссылка на исчезнувший файл, задача сразу в двух индексах) выносит тебе. Это
|
||||
идёт строкой доклада.
|
||||
чини `check --fix` — он детерминированно правит то, где истина однозначна
|
||||
(секция, заголовок, дубли, хук из индекса в файл, строка в чужом индексе,
|
||||
пометка `decomposed` у цели с задачами), а неоднозначное (ссылка на исчезнувший
|
||||
файл, задача сразу в двух индексах) печатает отдельной пометкой
|
||||
`НЕОДНОЗНАЧНО` — это тебе, и это идёт строкой доклада.
|
||||
|
||||
`--fix` правит **и файлы** — ровно в двух местах, где источник ровно один и
|
||||
выбирать не из чего: хук, оставшийся только в индексе, переезжает в мета-строку,
|
||||
и цель, у которой есть задачи, получает тег `decomposed`. Оба случая печатаются
|
||||
поимённо.
|
||||
|
||||
**Что механизировано, а что нет.** Критерии приёмки проверяются у задачи, взятой
|
||||
в набор (`sprint take` и `check` по задачам спринта): число пунктов — жёстко
|
||||
(меньше двух — отказ, больше пяти — замечание), наличие оракула — **эвристикой**
|
||||
по слову «оракул» в пункте. Настоящий оракул от слова «оракул» машина не
|
||||
отличает, поэтому эвристика даёт только замечание, и в докладе это называется
|
||||
как есть: «проверено число пунктов, годность оракулов — глазами».
|
||||
|
||||
Формат файла, мета-строки, слага, индексов и `REJECTED.md` —
|
||||
[references/task-format.md](references/task-format.md). Там же тест «готова к
|
||||
@@ -162,6 +217,13 @@ python3 $tk init [--dir D] [--sections …] [--plan-sections …] [--items …]
|
||||
пользователю до создания файлов. Порядок, отображение серьёзности и привязка к
|
||||
целям — [references/from-review.md](references/from-review.md).
|
||||
|
||||
### Прийти в репозиторий, где задачи уже как-то ведутся
|
||||
|
||||
Разовая операция: вывести каталог задач из старой раскладки беклога, `TODO.md`,
|
||||
заметок или списка шагов в плане — скилл `adopt`. Сюда же относится
|
||||
переименование транслитных слагов в английские: оно делается **одним проходом
|
||||
вместе с починкой перекрёстных ссылок**, а не по одному слагу.
|
||||
|
||||
### Декомпозиция и штурм идеи
|
||||
|
||||
[references/split.md](references/split.md). Обе операции превращают одну запись в
|
||||
@@ -175,7 +237,8 @@ python3 $tk init [--dir D] [--sections …] [--plan-sections …] [--items …]
|
||||
|
||||
- **протухший хук** — задача изменилась, а хук отвечает на старый вопрос;
|
||||
особенно после ответа на вопрос задачи: «Решено: …» на «почему это лежит в
|
||||
беклоге» уже не отвечает, хук переписывается;
|
||||
беклоге» уже не отвечает. Переписывается `edit <slug> --hook …` — он правит
|
||||
мета-строку файла и строку индекса заодно;
|
||||
- **вопрос, застрявший в прозе** — вынимается в раздел «Вопросы» плюс тег
|
||||
`question` (`edit --add-tag question`), иначе он не виден ни `list
|
||||
--questions`, ни правилу «задача с открытым вопросом в набор не берётся»;
|
||||
@@ -188,6 +251,40 @@ python3 $tk init [--dir D] [--sections …] [--plan-sections …] [--items …]
|
||||
такой-то агент»: это второй дом для правила выбора и путь понизить требования
|
||||
решением, принятым до проектирования. Снимается.
|
||||
|
||||
## Переносимость
|
||||
|
||||
Скилл независим от **языка программирования, сборки, CI и трекера**: он ничего
|
||||
не знает ни про Go, ни про npm, ни про конкретный багтрекер — задачи для него
|
||||
просто каталог markdown. Текст задач — русский (язык документации проекта);
|
||||
зашита только латиница слага.
|
||||
|
||||
- **Каталог задач** ищется цепочкой: `--dir` → **указатель в `CLAUDE.md`
|
||||
проекта** (его читаешь ты и передаёшь `--dir`; скрипт чужую документацию не
|
||||
разбирает) → `.tasks.json` вверх от текущего каталога → умолчания
|
||||
(`docs/tasks`, `tasks`, `doc/tasks`) вверх от текущего каталога, до корня
|
||||
репозитория. Не нашлось — код 3 и вопрос человеку, а не догадка: `init`
|
||||
заводит каталог **только** когда проект действительно новый.
|
||||
**В примерах `--dir` стоит намеренно:** каталог вне умолчаний иначе не
|
||||
находится, а вызов из подкаталога — обычное дело.
|
||||
- **Секции беклога** берутся из заголовков `##` индекса как есть; их количество
|
||||
и названия — дело проекта (умолчание `ядро` / `инфра`).
|
||||
- **Имена индексов и подкаталога** — параметры `init`, живут в
|
||||
`<tasks>/.tasks.json`. Ничего не зашито именем файла.
|
||||
|
||||
### Вызов из другого плагина
|
||||
|
||||
`$CLAUDE_PLUGIN_ROOT` раскрывается **только внутри своего плагина**: чужой
|
||||
контекст — пайплайн задачи, конвейер ревью, любой другой скилл — до `tasks.py`
|
||||
по этой переменной не дотянется. Поэтому контракт такой:
|
||||
|
||||
> **Проект называет команду учёта задач в своём `CLAUDE.md`** — целиком, готовой
|
||||
> к запуску строкой (слот 6 ниже). Вызывающий берёт её оттуда. Слота нет —
|
||||
> вызывающий **не выдумывает путь и не правит индекс руками**, а сообщает в
|
||||
> докладе, что закрытие/заведение остаётся за владельцем задач.
|
||||
|
||||
Так вызывающему не нужно знать ни про плагин, ни про его расположение: он знает
|
||||
проект, а проект знает команду.
|
||||
|
||||
## Слоты проекта
|
||||
|
||||
Скилл не знает ни языка программирования, ни сборки, ни CI, ни трекера — задачи
|
||||
@@ -207,6 +304,16 @@ python3 $tk init [--dir D] [--sections …] [--plan-sections …] [--items …]
|
||||
(деплой, выкладка наружу, удаление или перезапись данных).
|
||||
5. **Оракулы, которые в проекте вообще есть** — чем проверяется критерий
|
||||
приёмки: тест, команда, прогон на реальных данных, глазами по логу.
|
||||
6. **Команда учёта задач** — готовая строка, которой чужой контекст зовёт
|
||||
`tasks.py`, потому что путь к плагину ему неизвестен. Например:
|
||||
|
||||
```
|
||||
Команда учёта задач: python3 ~/.claude/plugins/marketplaces/av-dev-skills/\
|
||||
av-dev-tasks/skills/tasks/scripts/tasks.py --dir docs/tasks
|
||||
```
|
||||
|
||||
Слот заполняется один раз при подключении плагина. Он же отвечает на вопрос
|
||||
«кто закрывает задачу»: команду знает проект, зовёт её владелец спринта.
|
||||
|
||||
Ничего из этого скилл не угадывает: не нашёл — спрашивает пользователя, а не
|
||||
подставляет умолчание.
|
||||
|
||||
Reference in New Issue
Block a user