av-dev-tasks: починены находки ревью, добавлена адаптация чужого репозитория

- атомарность: sprint drop и move собирают план правок целиком и пишут
  одним проходом; раньше отказ на втором слаге оставлял первый файл
  переписанным при нетронутом индексе
- хук переехал в мета-строку файла: индекс стал производным, и check --fix
  больше не теряет текст, восстанавливая строку
- механизировано то, что было записано, но не проверялось: слаг спринта и
  автотег, отказ по факту непустого раздела вопросов, число критериев,
  пометка decomposed, покрытие причин
- reopen возвращает закрытую задачу: без него порядок «пайплайн доложил →
  приёмщик судит → владелец закрывает» был односторонним
- скилл adopt: приходит в чужой репозиторий и выводит заполненный каталог
  задач. На копии беклога healthlog — 12 целей, 38 задач, 36 переименований,
  86 ссылок в 36 файлах, check зелёный
This commit is contained in:
av
2026-08-03 11:45:23 +03:00
parent 9219f4a5cd
commit 20dca29add
8 changed files with 1768 additions and 329 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "av-dev-tasks",
"description": "Управление задачами как каталогом markdown-файлов: цели вместо приоритетов, спринт под одну цель с заморозкой набора, вопросы и блокеры, каденция «разбор — переоценка — набор». Заведение задач из диалога и из находок ревью, декомпозиция, штурм идей. Не выполняет задачи — этим занимается пайплайн проекта.",
"description": "Управление задачами как каталогом markdown-файлов: цели вместо приоритетов, спринт под одну цель с заморозкой набора, вопросы и блокеры, каденция «разбор — переоценка — набор». Заведение задач из диалога и из находок ревью, декомпозиция, штурм идей, разовая адаптация чужого репозитория под этот формат. Не выполняет задачи — этим занимается пайплайн проекта.",
"author": {
"name": "Anton Vakhrushev",
"email": "anwinged@gmail.com"
+122
View File
@@ -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` — результат строкой.
+31 -11
View File
@@ -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, строка возвращается в набор идущего
спринта (или в беклог, если спринта нет), строка кладбища снимается. Тело
восстанавливается **на момент удаления** — всё, что было дописано позже, живёт
только в коммите задачи, и это называется в докладе.
### Кто и по чему принимает
+127 -20
View File
@@ -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