Три изменения одной версией, потому что все три про одно — можно ли оценить задачу, не открывая код. PLAN.md → ROADMAP.md. Слово «план» значило в репозитории три разных вещи: оглавление целей, план реализации внутри задачи и PLAN.json разовой адаптации. Переименовано целиком — ключ конфига tasks.plan → tasks.roadmap, --index roadmap, --roadmap-sections, --roadmap. Старый ключ в docs/.pm.json не игнорируется молча: скрипт останавливается кодом 3 и называет переименование, иначе проект искал бы опечатку там, где на самом деле версия канона. Род работы — тег kind:feature|fix|chore|research, вторая ось поверх типа записи. В один префикс их не свести: идея бывает про функцию, эпик функцией и является. Дом — тег, потому что теги здесь единственный механизм разметки, а list --kind работает даром; цена принята — в строку индекса род не попадает. Словарь закрыт, иначе он разъедется на bug/bugfix/fix/defect. Отдельно легализован chore: у него «что станет наблюдаемо иначе» отвечается разработчику, а раньше такие задачи либо не заводились, либо придумывали себе пользовательскую пользу — и это второе хуже, оно проходит проверку. Раздел «Затрагивает» — границы, которых изменение касается: эндпоинт, таблица и миграция, формат на диске, публичный тип пакета. Без него задача оценивается по объёму текста, а не по объёму поверхности. Механизируется только наличие непустого раздела: полноту перечня машина не видит. Род и границы требуются к взятию в спринт, а не к заведению — тот же приём, что уже работает для критериев приёмки, и по той же причине. check о пропаже только напоминает: иначе два живых проекта покраснели бы на 98 задачах, заведённых до этого решения. Плюс правила языка задач: англицизм, у которого есть русское слово, заменяется; термин не из паспорта, архитектуры или конвенций вводится строкой или не употребляется; задача, которую не удаётся сказать просто, чаще всего не одна задача. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
26 KiB
Формат задач, целей и индексов
Заголовок, мета-блок и строку индекса ставит tasks.py add — руками их не
пишут. Этот файл описывает, что именно скрипт создаёт и что проверяет check;
тело задачи (одну фразу, критерии, рамки, контекст) дописывает агент.
Файл задачи
items/<slug>.md:
# Тай-брейк при равной полноте
- **Секция:** ядро — вышла из спринта: остаток писал нерешённое в журнал
- **Зачем:** порядок канонических форм берёт меньшее в 96% случаев — для накопительных это систематический недосчёт
- **Теги:** goal:merge-robustness, kind:fix, sprint:2026-08-03
При столкновении точек выигрывает более полная, но при равной полноте побеждает
последняя доставка — а она систематически беднее первой.
## Затрагивает
Таблица `points` и её миграция; правило слияния в приёме доставки; формат
отпечатка состояния на диске. Публичного контракта не трогает.
## Критерии приёмки
- повторный прогон свёртки даёт тот же отпечаток состояния — оракул: команда сверки
- накопительная метрика за сутки не уменьшается после повторной доставки — оракул: тест
- в логе видно, какая из двух точек выиграла и почему — оракул: глазами по логу прогона
## Рамки
Схема не трогается; данные только читаются; перезапуск сервиса допустим.
Связано: решение о канонической форме содержимого.
-
Заголовок H1 — он же заголовок строки в индексе, дословно. Тип кодируется префиксом
[goal]/[idea]/[epic]; обычная задача — без префикса. Отдельного поля типа нет: два места для одного факта разъезжаются, а префикс виден прямо в индексе, где и принимается решение «брать или не брать». -
Мета-блок — список сразу после заголовка, поле на строку. Обязательна секция, причина после тире желательна (именно она объясняет, почему задача здесь оказалась — в том числе «вышла из спринта: …»), «зачем» и теги опциональны. Порядок свободный, поле в одну строку. Нераспознанные поля сохраняются: скрипт правит свои и не трогает чужие.
-
«Зачем» отвечает на «зачем нужна эта задача» — состояние, остаток, боль. Не пересказ задачи: пересказ уже есть по ссылке. Живёт здесь, а не только в индексе: строка индекса его повторяет и производна от него,
checkсверяет,check --fixвосстанавливает пропавшую строку вместе с ним. Пока поле лежало только в индексе, штатная починка дрейфа теряла его молча и навсегда — а это единственное, по чему задачу выбирают, не открывая. -
Тело — одна фраза «что станет наблюдаемо иначе», затрагиваемые границы, критерии приёмки, рамки, контекст, ссылки. Пишется на языке документации проекта: предметно, без англицизмов, у которых есть русское слово, и без терминов, которых нет ни в паспорте, ни в архитектуре, ни в конвенциях (правило и его причина — в SKILL.md, раздел «Как написана задача»).
Мета одной строкой через · — прежняя форма. Она читается по-прежнему,
check называет её дрейфом, check --fix переписывает списком; поле Хук
при этом становится Зачем. Причина отказа от строки простая: с тремя полями
и длинным «зачем» строка уезжала за экран, а · приходилось запрещать в тексте
причины и самого «зачем».
Тело — не план реализации и не спецификация: принятое и реализованное переезжает в документацию проекта, а файл задачи удаляется.
Затрагивает
Перечень границ, которых изменение касается. Границей считается то, у чего есть внешняя сторона и цена изменения:
- эндпоинт, команда, форма ответа, код ответа;
- таблица, поле, миграция, формат на диске, формат сообщения в очереди;
- публичный тип или функция пакета, конфиг и его образцы;
- внешний сервис или библиотека, чьё поведение становится нужным.
Ничего из этого не трогается — так и пишется: «границ не трогает, изменение внутри одного узла». Это ответ, а не пустой раздел.
Границы, а не замысел. «Переписать хранилище на новый драйвер» — замысел;
таблица points и её миграция, эндпоинт POST /ingest — границы. Разница
проверяется вопросом «это можно назвать до того, как решено как делать?»: если
нет, строка описывает реализацию, и её место в предложении об изменении.
Свойства репозитория сюда не пишутся — по той же причине, что и в рамки:
имя таблицы стабильно, номер последней миграции протухает молча. Пишется
таблица points и её миграция, а не миграция 0042.
Что из этого механизировано. check и sprint take смотрят только на
наличие непустого раздела. Полнота перечня машине не видна: границу, которую
забыли назвать, она от отсутствующей не отличает. Раздела нет — отказ во взятии:
оценивать нечем.
У идей раздела нет — как и критериев: границы становятся известны, когда идея превращается в задачу.
Критерии приёмки
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сам ставит его цели, у которой задачи есть, а цель с тегом и без задач — прямое приглашение закрыть. - Цель живёт в
ROADMAP.mdи никогда — вBACKLOG.mdилиSPRINT.md.
Слаг
Латиница и цифры, kebab-case, без ведущих, хвостовых и двойных дефисов
(foo-bar, не -foo, a--b). Именуется по сути, а не по текущей
формулировке: заголовок будет переписан при переоценке, а слаг стоит в ссылках
из других задач, коммитов и черновиков. Транслита не заводим —
tie-break-equal-completeness, а не taj-brejk-pri-ravnoj-polnote: транслит
нечитаем для того, кто ищет по смыслу, и не сокращается.
Переименование слага — не правка, а перенос ссылок: делается одним атомарным проходом по всем местам, где слаг упомянут, иначе останутся битые ссылки, которых никто не проверяет.
Индексы
Строка везде одной формы:
- [Заголовок дословно](items/slug.md) — зачем
«Зачем» отвечает на «зачем нужна эта задача» одним предложением: состояние, остаток, боль. Пересказ первого абзаца бесполезен — он уже есть по ссылке.
| Файл | Что отвечает | Секции |
|---|---|---|
ROADMAP.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:<слаг>— цель, которой служит задача. Обязателен: задача без цели не попадёт ни в один спринт.kind:<род>— род работы:feature|fix|chore|research. Словарь закрыт, значение ровно одно. Обязателен у задачи (без негоsprint takeоткажет), у цели запрещён, у идеи и эпика необязателен. Ставитсяadd --kind/edit --kind;--kindзаменяет прежнее значение, а не добавляет второе. Смысл рода и почему он тегом, а не префиксом — в SKILL.md, раздел «Род работы».question— в файле есть неразобранный раздел «Вопросы».sprint:<слаг>— задача заведена в этом спринте; по нему отбирается первая порция разбора («урожай спринта»). Ставится сам: слаг спринта заводитsprint start(по умолчанию — дата начала, он же пишется вSPRINT.md), иaddпри открытом спринте помечает заводимое. Тег, который надо помнить ставить руками, не ставится никогда — а на нём висит правило «первая порция разбора — урожай прошедшего спринта».decomposed— на цели: разложена на задачи (см. «Файл цели»).
Отбор — list --tag a,b: перечисленные через запятую теги требуются все
сразу (это И, не ИЛИ). Тег, которого нет ни у одной задачи, list называет
вслух: молчаливый ноль читается как «таких задач нет», а чаще это опечатка.
Свои теги проект заводит свободно (партия ревью review-ГГГГ-ММ-ДД, тема,
источник) — словарь не фиксирован. В индексы теги не выносим: индексы
производны, отбор делает list --tag, а не глаза.
Тест «готова к взятию»
Задача готова, если из файла отвечаются четыре вопроса:
- Что станет наблюдаемо иначе, когда она сделана — снаружи: пользователю,
владельцу сервиса или разработчику. «Отрефакторить X» — не ответ; «перестанет
ломаться Y при Z» — ответ. У
kind:choreадресат — разработчик, и это законно: «уедет последний вызов устаревшего API» — ответ, а не отговорка. Род объявлен как раз затем, чтобы такие задачи не выдумывали себе пользовательскую пользу. - Каких границ это касается — раздел «Затрагивает». Без него задачу нельзя оценить: остаётся судить по длине текста.
- По чему видно, что закончено — критерии приёмки с оракулами.
- Какой цели она служит — тег
goal:и одна строка «почему именно этой».
Не отвечается первый, второй или третий вопрос → это идея ([idea]), её
место в штурме. Не отвечается четвёртый → либо цель есть и не проставлена, либо
задача не служит ничему — тогда её не надо заводить.
Отвечается всё, но задача не делается одним заходом и не мерджится целиком →
эпик ([epic]), сперва декомпозиция. Эпик временен и исчезает после
разбора; цель ([goal]) постоянна — не путать.
Тест применяется при заведении и при переоценке. К старым задачам, которых операция не касается, задним числом не применяется — беклог не переоформляют «заодно».