Последнее место, где поля писались одной строкой через ·: цель, дата начала и слаг спринта. После переезда меты задачи в блок эта строка осталась единственным исключением, а объяснять два формата дороже, чем иметь один. Старая шапка читается по-прежнему — GOAL_LINE берёт строку и с ведущим «- », SPRINT_SLUG_LINE и раньше искала по всей строке. Починки для неё нет и не нужно: SPRINT.md переписывается целиком на sprint start и очищается на sprint close, так что старая форма живёт не дольше идущего спринта. Форма заодно описана в task-format.md — до сих пор она жила только в коде, и человек, читавший документ формата, о ней не узнавал. Проверено на временном проекте: start → take → check → подсунутая старая шапка → drop → close; цель и слаг читаются в обеих формах. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
21 KiB
Формат задач, целей и индексов
Заголовок, мета-блок и строку индекса ставит tasks.py add — руками их не
пишут. Этот файл описывает, что именно скрипт создаёт и что проверяет check;
тело задачи (одну фразу, критерии, рамки, контекст) дописывает агент.
Файл задачи
items/<slug>.md:
# Тай-брейк при равной полноте
- **Секция:** ядро — вышла из спринта: остаток писал нерешённое в журнал
- **Зачем:** порядок канонических форм берёт меньшее в 96% случаев — для накопительных это систематический недосчёт
- **Теги:** goal:merge-robustness, sprint:2026-08-03
При столкновении точек выигрывает более полная, но при равной полноте побеждает
последняя доставка — а она систематически беднее первой.
## Критерии приёмки
- повторный прогон свёртки даёт тот же отпечаток состояния — оракул: команда сверки
- накопительная метрика за сутки не уменьшается после повторной доставки — оракул: тест
- в логе видно, какая из двух точек выиграла и почему — оракул: глазами по логу прогона
## Рамки
Схема не трогается; данные только читаются; перезапуск сервиса допустим.
Связано: решение о канонической форме содержимого.
-
Заголовок H1 — он же заголовок строки в индексе, дословно. Тип кодируется префиксом
[goal]/[idea]/[epic]; обычная задача — без префикса. Отдельного поля типа нет: два места для одного факта разъезжаются, а префикс виден прямо в индексе, где и принимается решение «брать или не брать». -
Мета-блок — список сразу после заголовка, поле на строку. Обязательна секция, причина после тире желательна (именно она объясняет, почему задача здесь оказалась — в том числе «вышла из спринта: …»), «зачем» и теги опциональны. Порядок свободный, поле в одну строку. Нераспознанные поля сохраняются: скрипт правит свои и не трогает чужие.
-
«Зачем» отвечает на «зачем нужна эта задача» — состояние, остаток, боль. Не пересказ задачи: пересказ уже есть по ссылке. Живёт здесь, а не только в индексе: строка индекса его повторяет и производна от него,
checkсверяет,check --fixвосстанавливает пропавшую строку вместе с ним. Пока поле лежало только в индексе, штатная починка дрейфа теряла его молча и навсегда — а это единственное, по чему задачу выбирают, не открывая. -
Тело — одна фраза «что станет наблюдаемо иначе», критерии приёмки, рамки, контекст, ссылки. Пишется на языке документации проекта.
Мета одной строкой через · — прежняя форма. Она читается по-прежнему,
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 |
что ушло без реализации и почему | — |
Шапку SPRINT.md пишет sprint start — тем же мета-блоком, что у задачи:
поле на строку, - **Цель:** [Заголовок](items/slug.md), - **Начат:** датой,
- **Спринт:** слагом, которым метится урожай. Прежняя форма (три поля одной
строкой через ·) читается по-прежнему и уходит сама: файл переписывается на
следующем sprint start и очищается на sprint close.
Секции — единственные заголовки ## в индексе: любой другой ## в
преамбуле проверка сочтёт секцией. Внутри секции беклога порядок значения не
имеет — порядка в беклоге нет вовсе.
Секции «блокеры» среди них нет. Блокер — состояние, а не полка: он живёт до
ответа человека, а следы остаются вопросами в файлах задач распущенного спринта.
Постоянно пустая секция со старой семантикой «разбираются пачками» противоречила
бы правилу «эскалируем немедленно», поэтому 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]) постоянна — не путать.
Тест применяется при заведении и при переоценке. К старым задачам, которых операция не касается, задним числом не применяется — беклог не переоформляют «заодно».