av-dev-tasks: починены находки ревью, добавлена адаптация чужого репозитория
- атомарность: sprint drop и move собирают план правок целиком и пишут одним проходом; раньше отказ на втором слаге оставлял первый файл переписанным при нетронутом индексе - хук переехал в мета-строку файла: индекс стал производным, и check --fix больше не теряет текст, восстанавливая строку - механизировано то, что было записано, но не проверялось: слаг спринта и автотег, отказ по факту непустого раздела вопросов, число критериев, пометка decomposed, покрытие причин - reopen возвращает закрытую задачу: без него порядок «пайплайн доложил → приёмщик судит → владелец закрывает» был односторонним - скилл adopt: приходит в чужой репозиторий и выводит заполненный каталог задач. На копии беклога healthlog — 12 целей, 38 задач, 36 переименований, 86 ссылок в 36 файлах, check зелёный
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "av-dev-tasks",
|
||||
"description": "Управление задачами как каталогом markdown-файлов: цели вместо приоритетов, спринт под одну цель с заморозкой набора, вопросы и блокеры, каденция «разбор — переоценка — набор». Заведение задач из диалога и из находок ревью, декомпозиция, штурм идей. Не выполняет задачи — этим занимается пайплайн проекта.",
|
||||
"description": "Управление задачами как каталогом markdown-файлов: цели вместо приоритетов, спринт под одну цель с заморозкой набора, вопросы и блокеры, каденция «разбор — переоценка — набор». Заведение задач из диалога и из находок ревью, декомпозиция, штурм идей, разовая адаптация чужого репозитория под этот формат. Не выполняет задачи — этим занимается пайплайн проекта.",
|
||||
"author": {
|
||||
"name": "Anton Vakhrushev",
|
||||
"email": "anwinged@gmail.com"
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
---
|
||||
name: adopt
|
||||
description: Прийти в чужой репозиторий и вывести каталог задач из того, что там уже есть — старая раскладка беклога (README-индекс, CLOSED-кладбище, транслитные слаги), TODO.md, россыпь заметок, раздел «планы» в README, список шагов в плане проекта. Сперва карта находок и целей человеку, запись только после подтверждения; слаги переименовываются в английские вместе с починкой перекрёстных ссылок. Использовать, когда просят перевести проект на этот формат задач, перенести беклог, адаптировать существующие заметки под цели и спринты. Разовая операция: дальше проект ведут скиллы tasks и session.
|
||||
---
|
||||
|
||||
# Адаптация чужого репозитория
|
||||
|
||||
Плагин приходит в проект, где задачи уже как-то ведутся, и **выводит** из
|
||||
имеющегося материала заполненный каталог задач: цели, задачи, кладбище, индексы.
|
||||
Операция разовая — после неё проект живёт скиллами `tasks` и `session`.
|
||||
|
||||
Вход какой угодно: старая раскладка `av-dev-backlog` (индекс `README.md`,
|
||||
кладбище `CLOSED.md`, приоритеты секциями, транслитные слаги, файлы рядом с
|
||||
индексом), `TODO.md`, россыпь заметок, раздел «планы» в `README.md`, список
|
||||
шагов в плане проекта.
|
||||
|
||||
## Три правила, из которых всё следует
|
||||
|
||||
1. **Сперва карта, потом файлы.** Человеку показывается, что найдено, как
|
||||
разложилось по целям и **что не разложилось**, — и только после подтверждения
|
||||
пишется хоть один файл. Это то же правило, что у интейка находок ревью:
|
||||
массовое заведение записей без подтверждения — самый дорогой отказ, потому
|
||||
что разгребает его потом переоценка.
|
||||
2. **Ничего не терять.** Исходный текст переезжает в тело, хук и причина
|
||||
сохраняются, кладбище переносится строка в строку. Переименование слага —
|
||||
не правка, а **перенос ссылок**: он делается одним проходом вместе с
|
||||
переименованием, иначе останутся битые ссылки, которых никто не проверяет.
|
||||
3. **Что не классифицировалось — назвать поимённо.** Проглоченный пункт
|
||||
выглядит как «всё перенеслось». Список «не разложилось» идёт в доклад
|
||||
целиком, с причиной по каждому пункту.
|
||||
|
||||
## Форма: карта — суждение — запись
|
||||
|
||||
Механику несёт `tasks.py adopt`, суждение — ты. Разделено ровно по границе
|
||||
«машина умеет / не умеет»:
|
||||
|
||||
```
|
||||
tk="$CLAUDE_PLUGIN_ROOT/skills/tasks/scripts/tasks.py"
|
||||
|
||||
python3 $tk adopt scan --from docs/backlog docs/plan.md TODO.md \
|
||||
--target docs/tasks --out tasks-adopt-plan.json # только чтение
|
||||
python3 $tk adopt apply --plan tasks-adopt-plan.json \
|
||||
--refs docs openspec CLAUDE.md README.md # запись
|
||||
```
|
||||
|
||||
`scan` ничего не пишет, кроме карты: он распознаёт раскладку, собирает записи,
|
||||
хуки, причины, кладбище, помечает похожее на транслит и на открытый вопрос в
|
||||
прозе, и **называет поимённо** то, что не разложилось. `apply` пишет каталог
|
||||
целиком одним проходом и чинит перекрёстные ссылки.
|
||||
|
||||
Между ними — твоя работа, которую машина не сделает:
|
||||
|
||||
- **английские слаги.** Перевести `taj-brejk-pri-ravnoj-polnote` в
|
||||
`tie-break-equal-completeness` может только тот, кто понимает смысл. `scan`
|
||||
честно говорит: проверить надо **все** слаги, признаки транслита — эвристика;
|
||||
- **цели.** Шаги плана — готовые цели **линии** (порядок и обоснование у них уже
|
||||
есть); тематические скопления задач — **кусты** («прочность слияния», «журнал
|
||||
и пересборка»). Предлагаешь ты, назначает человек;
|
||||
- **что вообще не задача.** Обоснование порядка шагов, абзац прозой, заголовок
|
||||
раздела — это не пункты беклога, и они уходят в «не разложилось» с причиной.
|
||||
|
||||
## Порядок
|
||||
|
||||
1. **Осмотрись.** Где лежат задачи, план, заметки; читается ли `CLAUDE.md`
|
||||
проекта — там может быть указатель на каталог. Секции беклога проекта
|
||||
(`--sections`) — по умолчанию `ядро,инфра`; если у проекта деление другое по
|
||||
существу, оно называется здесь, а не подгоняется под умолчание.
|
||||
2. **`adopt scan`** по всем источникам разом. Один прогон, одна карта: два
|
||||
прохода дадут два несогласованных состояния.
|
||||
3. **Заполни карту**: `slug` (английский), `section`, `goal` у каждой записи;
|
||||
список `goals` — из шагов плана и из кустов. Закрытый шаг плана целью не
|
||||
заводится. Пустой `goal` — законный исход только у идеи.
|
||||
4. **Покажи человеку карту** через `AskUserQuestion`, ≤3 вопроса за итерацию,
|
||||
рекомендация первым вариантом. Показывается: сколько записей, предлагаемые
|
||||
цели (линия и кусты) с обоснованием, спорные отнесения, список «не
|
||||
разложилось». Массовые механические решения (слаги, порядок строк) не
|
||||
выносятся — это механика.
|
||||
5. **`adopt apply`.** `--refs` перечисляет **всё**, где могут стоять ссылки на
|
||||
слаги: документация, архив изменений, `CLAUDE.md`, `README.md`. Скрипт
|
||||
посчитает и покажет, сколько ссылок поправлено и по каким слагам.
|
||||
6. **`tasks.py check`** и доклад.
|
||||
|
||||
`apply` отказывается писать поверх живого каталога и проверяет карту целиком
|
||||
**до** первой записи: неверная секция, дубль слага, цель, которой нет в карте —
|
||||
всё это отказ до того, как на диске появился хотя бы один файл.
|
||||
|
||||
## Переходное состояние — объявляется, а не заминается
|
||||
|
||||
Сразу после адаптации задачи в большинстве своём **не готовы к взятию**: у них
|
||||
нет критериев приёмки, а у части может не быть цели. Это нормально, но обязано
|
||||
быть названо, иначе следующий агент примет пустой беклог за поломку.
|
||||
|
||||
`apply` печатает состояние по факту: сколько задач без цели (это **ошибки**
|
||||
`check`) и сколько без критериев (`check` их ошибкой не считает, но `sprint
|
||||
take` такую задачу не возьмёт). Закрывается это **порциями переоценки** — шаг 3
|
||||
скилла `session`, 5–8 задач за порцию: проставить цели, превратить «готово,
|
||||
когда» в критерии с оракулами, вынуть вопросы из прозы в раздел «Вопросы».
|
||||
|
||||
Готовность к первому спринту — не «`check` зелёный», а «есть 2–5 критериев хотя
|
||||
бы у набора под одну цель».
|
||||
|
||||
## Чего адаптация не делает
|
||||
|
||||
- **Не удаляет источники.** Старый каталог остаётся на месте: сверить и убрать —
|
||||
дело человека, удалять чужое молча нельзя. В доклад идёт готовая команда.
|
||||
- **Не переписывает подписи ссылок.** `[docs/backlog](docs/tasks/BACKLOG.md)` —
|
||||
цель поправлена, текст остался; это правится глазами, и таких мест немного.
|
||||
- **Не сочиняет критерии приёмки и не придумывает цели**, которых в материале
|
||||
нет. Придуманная цель хуже отсутствующей: под неё соберут спринт.
|
||||
- **Не трогает историю.** В коммитах старые слаги остаются, и это нормально.
|
||||
|
||||
## Доклад
|
||||
|
||||
- Источники и что в каждом распознано (раскладка, индекс, кладбище, секции).
|
||||
- Сколько записей перенесено, сколько целей заведено (линия / кусты) и откуда
|
||||
каждая выведена.
|
||||
- **Переименования**: сколько слагов, сколько ссылок поправлено и в скольких
|
||||
файлах — числом, а не «поправлены ссылки».
|
||||
- **Не разложилось**: поимённо, с причиной.
|
||||
- Переходное состояние: сколько задач без цели, сколько без критериев, чем и за
|
||||
сколько порций закрывается.
|
||||
- `tasks.py check` — результат строкой.
|
||||
@@ -63,7 +63,11 @@ description: Ритуал между спринтами и ведение сам
|
||||
немедленно. Иначе спринт, из которого задачи вышли поштучно, выглядел бы штатно
|
||||
завершённым, а вопросы тихо ждали бы сессии.
|
||||
|
||||
**Отличать вопрос от застревания:**
|
||||
**Отличать вопрос от застревания.** Правило про остаток принадлежит управлению
|
||||
задачами: оно решает, **сделана задача или вышла**, а это исход планирования, не
|
||||
исполнения. **Ниже канонический текст; пайплайн проекта на него ссылается, а не
|
||||
пересказывает** — два экземпляра одного правила разъезжаются, и разъезжаются
|
||||
незаметно, потому что расхождение видно только на редком входе.
|
||||
|
||||
> Есть остаток, который доводится без ответа, — задача продолжается, вопрос
|
||||
> записывается в файл. Остатка нет — задача выходит из спринта.
|
||||
@@ -71,9 +75,11 @@ description: Ритуал между спринтами и ведение сам
|
||||
С двумя оговорками, без которых тест ошибается:
|
||||
|
||||
> **Остаток, который материализует нерешённое** — записывает в хранилище,
|
||||
> журнал, витрину или наружу состояние, зависящее от неотвеченного вопроса, —
|
||||
> **не остаток**. Решение поднимается до начала записи: откатить запись дороже,
|
||||
> чем подождать ответ, а иногда невозможно.
|
||||
> журнал, витрину **или наружу** состояние, зависящее от неотвеченного вопроса,
|
||||
> — **не остаток**. Решение поднимается до начала записи: откатить запись
|
||||
> дороже, чем подождать ответ, а иногда невозможно. «Наружу» — часть правила, а
|
||||
> не пример: выкладка, публикация и отправка данных третьей стороне не
|
||||
> откатываются тем более.
|
||||
|
||||
> **Пол для остатка:** остаток, из которого пропала польза, названная в хуке, —
|
||||
> это не сделанная задача, а вышедшая из спринта.
|
||||
@@ -125,15 +131,26 @@ description: Ритуал между спринтами и ведение сам
|
||||
прежде всего:
|
||||
|
||||
```
|
||||
python3 $tk check # с этого начинается любая сессия
|
||||
python3 $tk list --questions # шаг 1: что накопилось
|
||||
python3 $tk list --tag sprint:<слаг> # шаг 3: урожай прошедшего спринта, первая порция
|
||||
python3 $tk list --stale # шаг 3: дальше по залежалости
|
||||
python3 $tk list --goal <слаг> # шаг 4: кандидаты под названную цель
|
||||
python3 $tk sprint start --goal <слаг> # шаг 4
|
||||
python3 $tk sprint take <слаг> … # шаг 4: набор
|
||||
python3 $tk check --dir D # с этого начинается любая сессия
|
||||
python3 $tk list --dir D --questions # шаг 1: что накопилось
|
||||
python3 $tk list --dir D --tag sprint:<слаг> # шаг 3: урожай спринта, первая порция
|
||||
python3 $tk list --dir D --stale # шаг 3: дальше по залежалости
|
||||
python3 $tk list --dir D --goal <слаг> # шаг 4: кандидаты под названную цель
|
||||
python3 $tk sprint start --dir D --goal <слаг> # шаг 4: заводит и слаг спринта
|
||||
python3 $tk sprint take --dir D <слаг> … # шаг 4: набор
|
||||
python3 $tk sprint close --dir D # конец спринта; --dissolve при блокере
|
||||
python3 $tk reopen <слаг> --dir D --reason … # приёмка не сошлась после закрытия
|
||||
```
|
||||
|
||||
`D` — каталог задач проекта; цепочка его разрешения и вызов из чужого контекста
|
||||
описаны в скилле `tasks` («Переносимость»). **Коды выхода** — там же: 1 это
|
||||
дрейф в беклоге, 3 это «каталога нет», и ветвиться на них надо по-разному.
|
||||
|
||||
**Слаг спринта заводит `sprint start`** (по умолчанию — дата) и пишет его в
|
||||
`SPRINT.md`; всё заведённое при открытом спринте помечается `sprint:<слаг>`
|
||||
автоматически. Поэтому «первая порция — урожай прошедшего спринта» работает без
|
||||
чьей-либо памяти.
|
||||
|
||||
Правки задач делаются мутациями (`edit`, `move`, `close`), а не редактором:
|
||||
руками правится только тело файла. Это правило скилла `tasks`, здесь оно не
|
||||
пересказывается.
|
||||
@@ -181,6 +198,9 @@ python3 $tk sprint take <слаг> … # шаг 4: набор
|
||||
решает проект.
|
||||
6. **Ориентир по размеру спринта**, если он замерялся. Умолчание — 5–8 задач, и
|
||||
это **ориентир, а не закон**.
|
||||
7. **Команда учёта задач** — готовая строка вызова `tasks.py` (слот скилла
|
||||
`tasks`). Ею владелец спринта закрывает задачи и заводит урожай; чужой
|
||||
контекст сам путь к плагину не знает и знать не должен.
|
||||
|
||||
Числа проекта (сколько задач в спринте, сколько времени на задачу, каков прирост
|
||||
беклога) — предмет шага 2, а не константы этого скилла.
|
||||
|
||||
@@ -68,7 +68,9 @@
|
||||
задачи, заведённые за спринт; при урожае в 15 это две-три порции.
|
||||
- **Отбор порций по порядку:**
|
||||
1. **урожай спринта** — `list --tag sprint:<слаг>`: свежезаведённое ещё не
|
||||
проходило ни одной проверки на нужность;
|
||||
проходило ни одной проверки на нужность. Слаг спринта берётся из
|
||||
`SPRINT.md` (его завёл `sprint start`), тег на задачах проставлен
|
||||
автоматически при заведении — руками не метят и не вспоминают;
|
||||
2. дальше **по залежалости** — `list --stale`;
|
||||
3. по потребности — одна секция целиком, один тег (партия ревью), одна цель
|
||||
(`--goal`), список от пользователя.
|
||||
@@ -176,8 +178,9 @@
|
||||
4. **Набор показывается человеку до старта работ.** Показ — это и есть момент
|
||||
заморозки: после него набор не двигается.
|
||||
5. Задача, которой для взятия не хватает только критериев приёмки, дописывается
|
||||
здесь же — но если для критериев нужен ответ человека, это вопрос, и задача в
|
||||
набор не идёт.
|
||||
здесь же — 2–5 утверждений, у каждого назван оракул (меньше двух `sprint
|
||||
take` не примет). Но если для критериев нужен ответ человека, это вопрос, и
|
||||
задача в набор не идёт.
|
||||
|
||||
**Размер — ориентир, а не закон:** 5–8 задач. Можно взять больше, можно меньше —
|
||||
набор под цель важнее круглого числа; одна крупная задача спринтом тоже бывает.
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
след.
|
||||
|
||||
- **Сделана** — по определению готовности ниже. `close <slug> --implemented`:
|
||||
файл и строка удаляются, следом остаётся коммит.
|
||||
файл и строка удаляются, следом остаётся коммит. **Закрывает владелец спринта
|
||||
и только после вердикта приёмки** — см. «Кто и когда закрывает».
|
||||
- **Вышла из спринта** — `sprint drop <slug> --reason …`: возвращается в беклог
|
||||
с вопросом в файле и **без живого незакоммиченного предложения** — иначе при
|
||||
следующем взятии оно столкнётся с новым. Наработки, которые жалко терять,
|
||||
@@ -27,6 +28,13 @@
|
||||
«все сделаны»: иначе одна застрявшая задача держит спринт бесконечно. Затем
|
||||
`sprint close`.
|
||||
|
||||
**Урожай заводится при закрытии спринта, а не при закрытии задачи.** Это
|
||||
обязанность закрывающего: пройти по спискам находок от исполнителей и завести
|
||||
недостающее интейком скилла `tasks` — с дедупликацией и картой человеку. Заводимое
|
||||
метится тегом спринта само (`sprint:<слаг>`), поэтому первая порция следующей
|
||||
сессии поднимается одной командой `list --tag sprint:<слаг>`. Спринт, закрытый
|
||||
без этого шага, оставляет находки жить в отчётах — то есть нигде.
|
||||
|
||||
**Провал спринта.** Сработал блокер — спринт распускается (`sprint close
|
||||
--dissolve --reason …`), недоделанное возвращается в беклог, новый набор
|
||||
делается после ответа человека. Спринт не «ждёт»: ждать может человек, а
|
||||
@@ -44,8 +52,32 @@
|
||||
2. **Критерии приёмки проверены поимённо** — каждый со своим оракулом, исход по
|
||||
каждому назван. Это единственное, что добавляет управление задачами: пайплайн
|
||||
отвечает «сделано по правилам», критерии — «сделано то, что заказывали».
|
||||
3. **Урожай заведён** — вопросы и задачи, найденные по ходу, лежат в беклоге, а
|
||||
не в отчёте.
|
||||
3. **Находки по ходу отданы списком** — исполнитель обязан их **назвать**
|
||||
(каждую, с пометкой «заведена / не заведена: причина»), но **не обязан
|
||||
заводить**: заведение интерактивно, оно требует дедупликации против беклога и
|
||||
кладбища и решений человека. Обязанность **завести урожай** — на закрытии
|
||||
спринта, ниже. Так автономный исполнитель не оказывается одновременно обязан
|
||||
завести задачи и не вправе это сделать в одиночку.
|
||||
|
||||
### Кто и когда закрывает
|
||||
|
||||
**Задачу закрывает не пайплайн, а владелец спринта — после приёмки.** Порядок:
|
||||
|
||||
1. пайплайн доводит задачу до коммита и **докладывает исход**; файл задачи он не
|
||||
трогает — своей процедуры закрытия у него нет;
|
||||
2. приёмщик (не исполнитель) сверяет критерии поимённо и выносит вердикт;
|
||||
3. вердикт сошёлся — владелец спринта зовёт `close <slug> --implemented`
|
||||
командой учёта задач из `CLAUDE.md` проекта.
|
||||
|
||||
Обратный порядок ломает приёмку физически: закрытие **удаляет файл**, и
|
||||
приёмщику, нашедшему расхождение, возвращать нечего.
|
||||
|
||||
**Дорога назад существует и обязана быть названа.** Закрыли раньше вердикта, а
|
||||
приёмка не сошлась — `tasks.py reopen <slug> --reason "приёмка не сошлась: …"`:
|
||||
файл восстанавливается из истории git, строка возвращается в набор идущего
|
||||
спринта (или в беклог, если спринта нет), строка кладбища снимается. Тело
|
||||
восстанавливается **на момент удаления** — всё, что было дописано позже, живёт
|
||||
только в коммите задачи, и это называется в докладе.
|
||||
|
||||
### Кто и по чему принимает
|
||||
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
Слот заполняется один раз при подключении плагина. Он же отвечает на вопрос
|
||||
«кто закрывает задачу»: команду знает проект, зовёт её владелец спринта.
|
||||
|
||||
Ничего из этого скилл не угадывает: не нашёл — спрашивает пользователя, а не
|
||||
подставляет умолчание.
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
```markdown
|
||||
# Тай-брейк при равной полноте
|
||||
|
||||
**Секция:** ядро — вышла из спринта: остаток писал нерешённое в журнал · **Теги:** goal:merge-robustness, sprint:2026-08
|
||||
**Секция:** ядро — вышла из спринта: остаток писал нерешённое в журнал · **Хук:** порядок канонических форм берёт меньшее в 96% случаев — для накопительных это систематический недосчёт · **Теги:** goal:merge-robustness, sprint:2026-08-03
|
||||
|
||||
При столкновении точек выигрывает более полная, но при равной полноте побеждает
|
||||
последняя доставка — а она систематически беднее первой.
|
||||
@@ -35,9 +35,14 @@
|
||||
префикс виден прямо в индексе, где и принимается решение «брать или не брать».
|
||||
- **Мета-строка** — первая непустая строка после заголовка. Обязательна секция,
|
||||
причина после тире желательна (именно она объясняет, почему задача здесь
|
||||
оказалась — в том числе «вышла из спринта: …»), теги опциональны. Поля
|
||||
оказалась — в том числе «вышла из спринта: …»), хук и теги опциональны. Поля
|
||||
разделяются ` · `, порядок свободный. `·` — служебный разделитель: в тексте
|
||||
причины его быть не должно.
|
||||
причины и хука его быть не должно.
|
||||
- **Хук живёт здесь, а не только в индексе.** Строка индекса его повторяет и
|
||||
производна от него: `check` сверяет, `check --fix` восстанавливает пропавшую
|
||||
строку **вместе с хуком**. Пока хук лежал только в индексе, штатная починка
|
||||
дрейфа теряла его молча и навсегда — а хук это единственное, по чему задачу
|
||||
выбирают, не открывая.
|
||||
- **Тело** — одна фраза «что станет наблюдаемо иначе», критерии приёмки, рамки,
|
||||
контекст, ссылки. Пишется на языке документации проекта.
|
||||
|
||||
@@ -46,11 +51,18 @@
|
||||
|
||||
### Критерии приёмки
|
||||
|
||||
2–5 проверяемых утверждений, **у каждого назван оракул**. Не «работает
|
||||
корректно», а «повторный прогон даёт тот же отпечаток — оракул: команда сверки».
|
||||
Это не второе определение готовности, а проектная конкретизация вопроса «по чему
|
||||
видно, что закончено» из теста готовности ниже: там сказано «признак
|
||||
завершённости», здесь — «признак плюс чем проверяется».
|
||||
2–5 проверяемых утверждений **списком** `- …`, **у каждого назван оракул**. Не
|
||||
«работает корректно», а «повторный прогон даёт тот же отпечаток — оракул:
|
||||
команда сверки». Это не второе определение готовности, а проектная
|
||||
конкретизация вопроса «по чему видно, что закончено» из теста готовности ниже:
|
||||
там сказано «признак завершённости», здесь — «признак плюс чем проверяется».
|
||||
|
||||
**Что из этого механизировано.** `check` и `sprint take` считают пункты: меньше
|
||||
двух — отказ («— работает» одной строкой больше не проходит), больше пяти —
|
||||
замечание, обычно это признак, что задача крупнее задачи. Наличие оракула
|
||||
проверяется **эвристикой** — словом «оракул» в пункте, — и потому даёт только
|
||||
замечание: настоящий оракул от слова «оракул» машина не отличает, и делать вид,
|
||||
что проверено больше проверенного, хуже, чем не проверять вовсе.
|
||||
|
||||
**У идей критериев нет — именно поэтому они идеи.**
|
||||
|
||||
@@ -71,9 +83,14 @@
|
||||
### Вопросы
|
||||
|
||||
Неразобранное решение человека живёт разделом `## Вопросы` **плюс тегом
|
||||
`question`**. Тег — то, по чему вопрос виден снаружи файла (`list --questions`) и
|
||||
чем работает правило «задача с открытым вопросом в набор не берётся». Раздел без
|
||||
тега или тег без раздела — дрейф, `check` о нём скажет.
|
||||
`question`**. Раздел без тега или тег без раздела — дрейф, `check` о нём скажет.
|
||||
|
||||
**Судит факт, а не метка.** Отказ во взятии даёт **непустой раздел «Вопросы»**,
|
||||
независимо от того, стоит ли тег: иначе забывший тег проходил бы, а поставивший
|
||||
спотыкался — стимул ровно обратный записанному правилу. Тег производен: он нужен
|
||||
отбору снаружи файла (`list --questions`, `list --tag question`), и его
|
||||
отсутствие при непустом разделе — замечание, а не лазейка. Тег без раздела тоже
|
||||
отказ, но с другим советом: либо вопрос записан не туда, либо тег пора снять.
|
||||
|
||||
Ответ записывается в тело, тег снимается `edit <slug> --rm-tag question`, а хук
|
||||
переписывается: «Решено: …» на вопрос «почему это лежит в беклоге» уже не
|
||||
@@ -98,10 +115,12 @@
|
||||
- **Задачи цели здесь не перечисляются.** Перечень даёт
|
||||
`tasks.py list --goal <слаг>`; хранимый список стал бы третьим индексом и
|
||||
поехал бы на первой же закрытой задаче.
|
||||
- **Раздел «Завершение»** — то, по чему видно, что цель достигнута. Он же
|
||||
отличает «цель ещё не декомпозирована» от «все её задачи закрыты»: пометка
|
||||
вроде тега `decomposed` или строки в теле ставится, когда цель разложена на
|
||||
задачи.
|
||||
- **Раздел «Завершение»** — то, по чему видно, что цель достигнута.
|
||||
- **Тег `decomposed`** отличает «цель ещё не разобрана» от «все её задачи
|
||||
закрыты» — два состояния, у которых снаружи один и тот же признак: задач нет.
|
||||
Пометка именно **тегом**, а не строкой в теле: только так она проверяется.
|
||||
`check` требует его у цели без задач, `check --fix` сам ставит его цели, у
|
||||
которой задачи есть, а цель с тегом и без задач — прямое приглашение закрыть.
|
||||
- Цель живёт в `PLAN.md` и **никогда** — в `BACKLOG.md` или `SPRINT.md`.
|
||||
|
||||
## Слаг
|
||||
@@ -137,13 +156,27 @@
|
||||
|
||||
Секции — **единственные заголовки `##` в индексе**: любой другой `##` в
|
||||
преамбуле проверка сочтёт секцией. Внутри секции беклога порядок значения не
|
||||
имеет — порядка в беклоге нет вовсе. В **линии** плана порядок значим и
|
||||
имеет — порядка в беклоге нет вовсе.
|
||||
|
||||
**Секции «блокеры» среди них нет.** Блокер — состояние, а не полка: он живёт до
|
||||
ответа человека, а следы остаются вопросами в файлах задач распущенного спринта.
|
||||
Постоянно пустая секция со старой семантикой «разбираются пачками» противоречила
|
||||
бы правилу «эскалируем немедленно», поэтому `init` её не заводит, а `check`
|
||||
говорит о ней в чужом беклоге. Переезжаешь с такой секцией — удали её. В **линии** плана порядок значим и
|
||||
обосновывается прозой; двигают строку `move <slug> --section линия --after
|
||||
<другой>`.
|
||||
|
||||
Индексы **производны**: расходятся с файлом — правим индексы (`check --fix`).
|
||||
Строку руками не пишут.
|
||||
|
||||
Отсюда же ответ на «а если оборвётся посередине». Мутация сперва проверяет всё
|
||||
и складывает правки, и только потом пишет: сначала все временные файлы, потом
|
||||
переименования подряд. Полной транзакции на несколько файлов файловая система не
|
||||
даёт, но окно сжато до цепочки переименований, а **всё, что в нём может
|
||||
разъехаться, — производное**: файлы целы, индексы восстанавливает `check --fix`.
|
||||
Поэтому отказ на второй задаче из пяти не оставляет первую переписанной при
|
||||
нетронутых индексах.
|
||||
|
||||
`SPRINT.md` и есть артефакт заморозки: без него набор существует только в
|
||||
контексте сессии, и нарушение заморозки ненаблюдаемо.
|
||||
|
||||
@@ -174,7 +207,16 @@
|
||||
попадёт ни в один спринт.
|
||||
- `question` — в файле есть неразобранный раздел «Вопросы».
|
||||
- `sprint:<слаг>` — задача заведена в этом спринте; по нему отбирается первая
|
||||
порция разбора («урожай спринта»).
|
||||
порция разбора («урожай спринта»). **Ставится сам**: слаг спринта заводит
|
||||
`sprint start` (по умолчанию — дата начала, он же пишется в `SPRINT.md`), и
|
||||
`add` при открытом спринте помечает заводимое. Тег, который надо помнить
|
||||
ставить руками, не ставится никогда — а на нём висит правило «первая порция
|
||||
разбора — урожай прошедшего спринта».
|
||||
- `decomposed` — на цели: разложена на задачи (см. «Файл цели»).
|
||||
|
||||
Отбор — `list --tag a,b`: перечисленные через запятую теги требуются **все
|
||||
сразу** (это И, не ИЛИ). Тег, которого нет ни у одной задачи, `list` называет
|
||||
вслух: молчаливый ноль читается как «таких задач нет», а чаще это опечатка.
|
||||
|
||||
Свои теги проект заводит свободно (партия ревью `review-ГГГГ-ММ-ДД`, тема,
|
||||
источник) — словарь не фиксирован. В индексы теги не выносим: индексы
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user