Два независимых сабагента на av-dev-pm и av-dev-pipeline. Две находки нашли оба. Главная — моя же перестановка закрытия за коммит сломала reopen и батч. close печатал «дорога назад из git», а reopen искал коммит удаления, которого в новом порядке ещё нет: шаг 11 последний, учёт остаётся незакоммиченным. Проверено прогоном — отказ кодом 2 на свежезакрытой задаче. Тем же грязным деревом ломались rebase и worktree remove в батче: каждая закрывшая задачу ветка уехала бы в провалившиеся. Починено с обеих сторон: reopen берёт текст из HEAD, если коммита удаления нет, а шаг 11 коммитит учёт вторым коммитом. Вторая — канонический пример docs/.pm.json убивал tasks.py. Четыре документа показывали ключ tasks.sections, которого скрипт не знает: неизвестный ключ это код 3 на любой команде. Проект, заведённый по канону дословно, остался бы без работы с задачами, а docs.py при этом печатал «канон соблюдён». Секции живут в заголовках индекса и второго дома не получают. Остальные восемнадцать: init писал конфиг в упразднённый .tasks.json; looks_like_tasks не видел переименованный индекс; урожай спринта терял автотег после sprint close; ответ на вопрос по инструкции оставлял задачу незабираемой; adopt требовал недостижимого зелёного; путь отчёта триажа не переживал archive; review-specs не имел режима для стыка после слияния; три остатка «шаг 9а» несли предкоммитную позицию закрытия; sprint.md отрицал сам себя в пункте «Сделана». Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
20 KiB
Формат задач, целей и индексов
Заголовок, мета-строку и строку индекса ставит tasks.py add — руками их не
пишут. Этот файл описывает, что именно скрипт создаёт и что проверяет check;
тело задачи (одну фразу, критерии, рамки, контекст) дописывает агент.
Файл задачи
items/<slug>.md:
# Тай-брейк при равной полноте
**Секция:** ядро — вышла из спринта: остаток писал нерешённое в журнал · **Хук:** порядок канонических форм берёт меньшее в 96% случаев — для накопительных это систематический недосчёт · **Теги:** goal:merge-robustness, sprint:2026-08-03
При столкновении точек выигрывает более полная, но при равной полноте побеждает
последняя доставка — а она систематически беднее первой.
## Критерии приёмки
- повторный прогон свёртки даёт тот же отпечаток состояния — оракул: команда сверки
- накопительная метрика за сутки не уменьшается после повторной доставки — оракул: тест
- в логе видно, какая из двух точек выиграла и почему — оракул: глазами по логу прогона
## Рамки
Схема не трогается; данные только читаются; перезапуск сервиса допустим.
Связано: решение о канонической форме содержимого.
- Заголовок H1 — он же заголовок строки в индексе, дословно. Тип кодируется
префиксом
[goal]/[idea]/[epic]; обычная задача — без префикса. Отдельного поля типа нет: два места для одного факта разъезжаются, а префикс виден прямо в индексе, где и принимается решение «брать или не брать». - Мета-строка — первая непустая строка после заголовка. Обязательна секция,
причина после тире желательна (именно она объясняет, почему задача здесь
оказалась — в том числе «вышла из спринта: …»), хук и теги опциональны. Поля
разделяются
·, порядок свободный.·— служебный разделитель: в тексте причины и хука его быть не должно. - Хук живёт здесь, а не только в индексе. Строка индекса его повторяет и
производна от него:
checkсверяет,check --fixвосстанавливает пропавшую строку вместе с хуком. Пока хук лежал только в индексе, штатная починка дрейфа теряла его молча и навсегда — а хук это единственное, по чему задачу выбирают, не открывая. - Тело — одна фраза «что станет наблюдаемо иначе», критерии приёмки, рамки, контекст, ссылки. Пишется на языке документации проекта.
Тело — не план реализации и не спецификация: принятое и реализованное переезжает в документацию проекта, а файл задачи удаляется.
Критерии приёмки
2–5 проверяемых утверждений списком - …, у каждого назван оракул. Не
«работает корректно», а «повторный прогон даёт тот же отпечаток — оракул:
команда сверки». Это не второе определение готовности, а проектная
конкретизация вопроса «по чему видно, что закончено» из теста готовности ниже:
там сказано «признак завершённости», здесь — «признак плюс чем проверяется».
Что из этого механизировано. check и sprint take считают пункты: меньше
двух — отказ («— работает» одной строкой больше не проходит), больше пяти —
замечание, обычно это признак, что задача крупнее задачи. Наличие оракула
проверяется эвристикой — словом «оракул» в пункте, — и потому даёт только
замечание: настоящий оракул от слова «оракул» машина не отличает, и делать вид,
что проверено больше проверенного, хуже, чем не проверять вовсе.
У идей критериев нет — именно поэтому они идеи.
Критерии — пол, но расхождение с ними есть дефект критериев. Если приёмщик видит, что критерии закрыты, а суть задачи не достигнута, он правит критерии и возвращает задачу исполнителю, а не держит невидимое сверх-требование. Иначе исполнитель никогда не знает, закончил ли, и мотивирован занижать критерии заранее.
Рамки
Одна строка: чего касаться нельзя, что перезапускается, что считается необратимым, трогается ли схема данных. Свойства репозитория сюда не пишутся — номер последней миграции, версия зависимости, хеш: в лежалой задаче они протухают молча и становятся ложной рамкой. Снимок берётся при постановке, а не при заведении.
Вопросы
Неразобранное решение человека живёт разделом ## Вопросы плюс тегом
question. Раздел без тега или тег без раздела — дрейф, check о нём скажет.
Судит факт, а не метка. Отказ во взятии даёт непустой раздел «Вопросы»,
независимо от того, стоит ли тег: иначе забывший тег проходил бы, а поставивший
спотыкался — стимул ровно обратный записанному правилу. Тег производен: он нужен
отбору снаружи файла (list --questions, list --tag question), и его
отсутствие при непустом разделе — замечание, а не лазейка. Тег без раздела тоже
отказ, но с другим советом: либо вопрос записан не туда, либо тег пора снять.
Ответ на вопрос — три правки, и первая обязательна. Раздел «Вопросы»
опустошается: ответ переезжает в тело решением, а не остаётся вопросом рядом с
ответом. Затем снимается тег (edit <slug> --rm-tag question) и переписывается
хук: «Решено: …» на вопрос «почему это лежит в беклоге» уже не отвечает.
Порядок именно такой, потому что судит раздел, а не тег. sprint take
смотрит в непустой раздел и откажет взять задачу даже со снятым тегом, а check
на снятый тег при непустом разделе посоветует тег вернуть. Снять тег, не
опустошив раздел, — значит закольцевать себя между двумя советами.
Файл цели
# [goal] Прочность слияния
**Секция:** кусты · **Теги:** decomposed
Ради чего: точки из разных доставок сходятся в один часовой объект, и сегодня
исход столкновения зависит от порядка доставки, а не от содержания.
## Завершение
Достигнута, когда исход слияния не зависит ни от порядка, ни от времени
доставки, и это подтверждено повторным прогоном на живом корпусе.
- Задачи цели здесь не перечисляются. Перечень даёт
tasks.py list --goal <слаг>; хранимый список стал бы третьим индексом и поехал бы на первой же закрытой задаче. - Раздел «Завершение» — то, по чему видно, что цель достигнута.
- Тег
decomposedотличает «цель ещё не разобрана» от «все её задачи закрыты» — два состояния, у которых снаружи один и тот же признак: задач нет. Пометка именно тегом, а не строкой в теле: только так она проверяется.checkнапоминает о нём у цели без задач замечанием — неразобранная цель законна и зелёного прогона не ломает;check --fixсам ставит его цели, у которой задачи есть, а цель с тегом и без задач — прямое приглашение закрыть. - Цель живёт в
PLAN.mdи никогда — вBACKLOG.mdилиSPRINT.md.
Слаг
Латиница и цифры, kebab-case, без ведущих, хвостовых и двойных дефисов
(foo-bar, не -foo, a--b). Именуется по сути, а не по текущей
формулировке: заголовок будет переписан при переоценке, а слаг стоит в ссылках
из других задач, коммитов и черновиков. Транслита не заводим —
tie-break-equal-completeness, а не taj-brejk-pri-ravnoj-polnote: транслит
нечитаем для того, кто ищет по смыслу, и не сокращается.
Переименование слага — не правка, а перенос ссылок: делается одним атомарным проходом по всем местам, где слаг упомянут, иначе останутся битые ссылки, которых никто не проверяет.
Индексы
Строка везде одной формы:
- [Заголовок дословно](items/slug.md) — хук
Хук отвечает на «почему это лежит в беклоге» одним предложением: состояние, остаток, боль. Пересказ первого абзаца бесполезен — он уже есть по ссылке.
| Файл | Что отвечает | Секции |
|---|---|---|
PLAN.md |
какие есть цели, в каком порядке идёт линия и почему | линия (упорядоченная) и кусты |
BACKLOG.md |
что можно взять — только задачи | секции проекта (по умолчанию ядро/инфра) |
SPRINT.md |
какая цель и какой набор под неё | одна: «Набор» |
REJECTED.md |
что ушло без реализации и почему | — |
Секции — единственные заголовки ## в индексе: любой другой ## в
преамбуле проверка сочтёт секцией. Внутри секции беклога порядок значения не
имеет — порядка в беклоге нет вовсе.
Секции «блокеры» среди них нет. Блокер — состояние, а не полка: он живёт до
ответа человека, а следы остаются вопросами в файлах задач распущенного спринта.
Постоянно пустая секция со старой семантикой «разбираются пачками» противоречила
бы правилу «эскалируем немедленно», поэтому init её не заводит, а check
говорит о ней в чужом беклоге. Переезжаешь с такой секцией — удали её. В линии плана порядок значим и
обосновывается прозой; двигают строку move <slug> --section линия --after <другой>.
Индексы производны: расходятся с файлом — правим индексы (check --fix).
Строку руками не пишут.
Отсюда же ответ на «а если оборвётся посередине». Мутация сперва проверяет всё
и складывает правки, и только потом пишет: сначала все временные файлы, потом
переименования подряд. Полной транзакции на несколько файлов файловая система не
даёт, но окно сжато до цепочки переименований, а всё, что в нём может
разъехаться, — производное: файлы целы, индексы восстанавливает check --fix.
Поэтому отказ на второй задаче из пяти не оставляет первую переписанной при
нетронутых индексах.
SPRINT.md и есть артефакт заморозки: без него набор существует только в
контексте сессии, и нарушение заморозки ненаблюдаемо.
REJECTED.md
Туда уходит задача, покинувшая беклог без реализации. Строку пишет
tasks.py close --reason, а check следит за форматом:
- 2026-07-23 `versii-kachestvo-repaki` — Версии и качество одного тайтла.
Причина: калибровка болей — не боль, ни разу не возникло за полгода.
Была секция: инфра.
Реализованные сюда не попадают: у них остаётся коммит и документация. У выкинутой не остаётся ничего — и через квартал она возвращается тем же текстом. Это первое место, куда смотрит дедупликация при заведении.
Запись не запрещает завести задачу заново: изменился контекст — заводим и ссылаемся на строку, объясняя, что изменилось.
Теги
Единственный механизм разметки, потому что list --tag уже умеет отбирать по
ним порцию разбора. Отдельных полей мета-строки под это не заводим.
goal:<слаг>— цель, которой служит задача. Обязателен: задача без цели не попадёт ни в один спринт.question— в файле есть неразобранный раздел «Вопросы».sprint:<слаг>— задача заведена в этом спринте; по нему отбирается первая порция разбора («урожай спринта»). Ставится сам: слаг спринта заводитsprint start(по умолчанию — дата начала, он же пишется вSPRINT.md), иaddпри открытом спринте помечает заводимое. Тег, который надо помнить ставить руками, не ставится никогда — а на нём висит правило «первая порция разбора — урожай прошедшего спринта».decomposed— на цели: разложена на задачи (см. «Файл цели»).
Отбор — list --tag a,b: перечисленные через запятую теги требуются все
сразу (это И, не ИЛИ). Тег, которого нет ни у одной задачи, list называет
вслух: молчаливый ноль читается как «таких задач нет», а чаще это опечатка.
Свои теги проект заводит свободно (партия ревью review-ГГГГ-ММ-ДД, тема,
источник) — словарь не фиксирован. В индексы теги не выносим: индексы
производны, отбор делает list --tag, а не глаза.
Тест «готова к взятию»
Задача готова, если из файла отвечаются три вопроса:
- Что станет наблюдаемо иначе, когда она сделана — снаружи: пользователю, владельцу сервиса или разработчику. «Отрефакторить X» — не ответ; «перестанет ломаться Y при Z» — ответ.
- По чему видно, что закончено — критерии приёмки с оракулами.
- Какой цели она служит — тег
goal:и одна строка «почему именно этой».
Не отвечается первый или второй вопрос → это идея ([idea]), её место в
штурме. Не отвечается третий → либо цель есть и не проставлена, либо задача не
служит ничему — тогда её не надо заводить.
Отвечается всё, но задача не делается одним заходом и не мерджится целиком →
эпик ([epic]), сперва декомпозиция. Эпик временен и исчезает после
разбора; цель ([goal]) постоянна — не путать.
Тест применяется при заведении и при переоценке. К старым задачам, которых операция не касается, задним числом не применяется — беклог не переоформляют «заодно».