Команда stage была дефектна по шести пунктам, и все шесть подтверждены прогоном: не звала raw_last (переход оставлял каталог красным), не переписывала шапку беклога (индекс продолжал объявлять прежнюю стадию), шла в обход write_config, молча пропускала файлы с непересобираемой метой, ломалась на беклоге без заголовков и схлопывала полки при первом объявлении стадии. Объявление и смена разведены: объявление беклога не трогает вовсе, смена трогает состав секций только по явному --sections, а слить полки скрипт не берётся ни в одном случае. Абзац шапки размечен парой «стадия», и расхождение с конфигом стало обычным дрейфом. Отказ по недостающей строке индекса запирал запись, пережившую упразднение роадмапа: edit, close и reopen теперь заводят или пропускают строку сами. Прочее: регистр stage нормализуется при чтении; --fix снимает мёртвые теги и у неразобранных записей; move отказывает переставлять сырьё; adopt держит место сырья; docs.py bump двигает одну запись журнала за раз; tasks.py получил перечень упразднённых адресов, и гейт наконец видит собственное упразднение ROADMAP.md. Запись «Версия 3» переписана по прогону на игрушечном проекте: прежний порядок шагов был неисполним. Закрыты дыры модели стадий (пересмотр плана стройки стал сценарием, приёмка отвязана от груминга, from-review, research и adopt получили развилку по стадии, перечень осей пересчитан) и находки, старшие этой сессии: review-triage получил режим без метки, три списка проектных копий сведены к дому с проверяемыми копиями, пять пересказов правил стали помеченными копиями или ссылками, language.md перестал объявлять юрисдикцию над чужим плагином.
27 KiB
Формат записей и индекса
Заголовок, мета-блок и строку индекса ставит tasks.py add — руками их не
пишут. Этот файл описывает общую форму любой записи и то, что проверяет
check; тело дописывает агент.
Чем разделы тела отличаются от типа к типу, какой алгоритм у каждого типа и что у него обязательно — отдельным файлом на тип:
| Тип | Файл | Одной строкой |
|---|---|---|
✨ feature |
task-feature.md | снаружи появляется то, чего не было |
🐞 fix |
task-fix.md | поведение расходится с заявленным |
🧹 chore |
task-chore.md | обслуживание, поведение не меняется |
🔬 research |
task-research.md | исход — знание, а не изменение |
Файл записи
items/<slug>.md:
# 🐞 Не отбрасывать молча лишние символы в ходе
- **Тип:** fix
- **Категория:** Ядро — вернулась из работы: остаток писал нерешённое в журнал
- **Зачем:** ввод «а1б2» ходит в a1 — игрок не видит, что ошибся, и винит игру
Разбор хода читает первые два символа и молча выбрасывает остаток строки.
## Воспроизведение
Ввести `а1б2` в свой ход: программа ходит в `a1` и ничего не сообщает.
Ожидалось — отказ с ошибкой разбора.
## Затрагивает
Разбор строки хода; текст ошибки в выводе партии. Формат сохранения партии
не трогается.
## Критерии приёмки
- ввод «а1б2» отвергается с ошибкой — оракул: тест разбора
- ввод «а1» принимается по-прежнему — оракул: тест разбора
## Рамки
Схема не трогается; данные только читаются; перезапуск допустим.
Связано: решение о канонической форме содержимого.
- Заголовок H1 — он же заголовок строки в индексе, дословно. Начинается
эмодзи типа, и она производна: её ставит
addи чинитcheck --fixпо полю меты. Второго дома у типа нет — эмодзи это его отображение, как строка индекса это отображение файла. - Форма заголовка — по типу.
feature,fixиchoreотвечают на «что нужно сделать», глаголом в неопределённой форме, перед ним допускается «не»;researchназывает предмет разведки и формы действия не несёт намеренно. Почему так — SKILL.md, «Как написана задача».checkсчитает заголовки не в форме действия и печатает число в здоровье; годность формулировки смотрит агентtask-form. - Мета-блок — список сразу после заголовка, поле на строку. Обязательны тип и место, причина после тире желательна (именно она объясняет, почему задача здесь оказалась — в том числе «вернулась из работы: …»), «зачем» и теги необязательны. Нераспознанные поля сохраняются: скрипт правит свои и не трогает чужие.
- Тип — первым полем. Он решает, что у записи вообще может быть: какие
разделы обязательны и берётся ли она в работу, — и читается раньше всего
остального. Словарь закрыт:
feature|fix|chore|research. Не подходит ни один — это сигнал, что в записи их два и её надо разделить. - «Зачем» отвечает на «зачем нужна эта задача» — состояние, остаток, боль.
Не пересказ задачи: пересказ уже есть по ссылке. Живёт здесь, а не только в
индексе: строка индекса его повторяет и производна от него,
checkсверяет,check --fixвосстанавливает пропавшую строку вместе с ним. Пока поле лежало только в индексе, штатная починка дрейфа теряла его молча и навсегда — а это единственное, по чему задачу выбирают, не открывая. - Тело — одна фраза «что станет наблюдаемо иначе», дальше разделы по схеме типа. Пишется на языке документации проекта: предметно, без англицизмов, у которых есть русское слово, и без терминов, которых нет ни в паспорте, ни в архитектуре, ни в конвенциях (правило и его причина — в SKILL.md, раздел «Как написана задача»).
Тело — не план реализации и не спецификация: принятое и реализованное переезжает в документацию проекта, а файл задачи удаляется.
Поле места: «Категория»
Поле называет секцию беклога, в которой числится строка — полку домена
(Ядро, Инфра, …), куда задачу положили и куда вернут, если она уйдёт в работу
и вернётся. На стройке секция одна, и поле называет её же: различать ей
нечего, но производность от заголовка индекса сохраняется и там.
Прежнее имя поля — «Секция»: так оно называлось у целей, указывая на часть
роадмапа. Разбор его по-прежнему принимает, check называет дрейфом, check --fix переименовывает.
Имя самого места принадлежит заголовку индекса — файл на него лишь ссылается, и принадлежность сверяется по нижнему регистру.
Прежние формы, которые читаются, но не пишутся
Всё это check называет дрейфом, а check --fix переписывает:
| Было | Стало |
|---|---|
префикс [goal] / [idea] в H1 |
поле Тип + эмодзи в H1; [idea] → research |
тег kind:<род> |
поле Тип (род работы стал типом) |
| поле Секция | поле Категория |
теги goal:<слаг> и decomposed |
сняты: целей больше нет |
| поле Хук | поле Зачем |
мета одной строкой через · |
мета списком, поле на строку |
Чего --fix не делает сам — решает за человека, каким быть типу. Случаев
три, и все три уезжают пометкой НЕОДНОЗНАЧНО: тип, которого неоткуда взять
(feature от chore машина не отличает); тип вне словаря; и запись типа goal
— целей больше нет, а во что превращается эта, в задачу или в ничто, машина не
знает. Подставленное наугад значение врало бы ровно там, где по нему принимают
решение. Мёртвые теги и имя поля места при этом снимаются у любой записи,
включая ту, чей тип остался неразобранным.
Затрагивает
Перечень границ, которых изменение касается. Границей считается то, у чего есть внешняя сторона и цена изменения:
- эндпоинт, команда, форма ответа, код ответа;
- таблица, поле, миграция, формат на диске, формат сообщения в очереди;
- публичный тип или функция пакета, конфиг и его образцы;
- внешний сервис или библиотека, чьё поведение становится нужным.
Ничего из этого не трогается — так и пишется: «границ не трогает, изменение внутри одного узла». Это ответ, а не пустой раздел.
Границы, а не замысел. «Переписать хранилище на новый драйвер» — замысел;
таблица points и её миграция, эндпоинт POST /ingest — границы. Разница
проверяется вопросом «это можно назвать до того, как решено как делать?»: если
нет, строка описывает реализацию, и её место в предложении об изменении.
Свойства репозитория сюда не пишутся — по той же причине, что и в рамки:
имя таблицы стабильно, номер последней миграции протухает молча. Пишется
таблица points и её миграция, а не миграция 0042.
Что из этого механизировано. ready смотрит только на
наличие непустого раздела. Полнота перечня машине не видна: границу, которую
забыли назвать, она от отсутствующей не отличает. Раздела нет — отказ во взятии:
оценивать нечем.
У research раздела нет — её границы становятся известны, когда из разведки
родятся задачи.
Критерии приёмки
2–5 проверяемых утверждений списком - …, у каждого назван оракул. Не
«работает корректно», а «повторный прогон даёт тот же отпечаток — оракул:
команда сверки». Это не второе определение сделанного, а проектная
конкретизация вопроса «по чему видно, что закончено» из теста готовности ниже:
там сказано «признак завершённости», здесь — «признак плюс чем проверяется».
Что из этого механизировано. ready считает пункты: меньше
двух — отказ («— работает» одной строкой больше не проходит), больше пяти —
замечание, обычно это признак, что задача крупнее задачи. Наличие оракула
проверяется эвристикой — словом «оракул» в пункте, — и потому даёт только
замечание: настоящий оракул от слова «оракул» машина не отличает, и делать вид,
что проверено больше проверенного, хуже, чем не проверять вовсе.
У research критериев нет — её приёмка это записанный ответ, и описывается
она разделами «Вопрос» и «Куда ляжет ответ».
Критерии — пол, но расхождение с ними есть дефект критериев. Если приёмщик видит, что критерии закрыты, а суть задачи не достигнута, он правит критерии и возвращает задачу исполнителю, а не держит невидимое сверх-требование. Иначе исполнитель никогда не знает, закончил ли, и мотивирован занижать критерии заранее.
Рамки
Одна строка: чего касаться нельзя, что перезапускается, что считается необратимым, трогается ли схема данных. Раздел допустим у любого типа задачи и ни у одного не обязателен. Свойства репозитория сюда не пишутся — номер последней миграции, версия зависимости, хеш: в лежалой задаче они протухают молча и становятся ложной рамкой. Снимок берётся при постановке, а не при заведении.
Вопросы
Неразобранное решение человека живёт разделом ## Вопросы плюс тегом
question. Раздел без тега или тег без раздела — дрейф, check о нём скажет.
Раздел Вопросы (о решении человека) и раздел Вопрос у research (предмет
разведки) — разные вещи и разные слова: первый блокирует взятие, второй его
разрешает.
Судит факт, а не метка. Отказ во взятии даёт непустой раздел «Вопросы»,
независимо от того, стоит ли тег: иначе забывший тег проходил бы, а поставивший
спотыкался — стимул ровно обратный записанному правилу. Тег производен: он нужен
отбору снаружи файла (list --questions, list --tag question), и его
отсутствие при непустом разделе — замечание, а не лазейка. Тег без раздела тоже
отказ, но с другим советом: либо вопрос записан не туда, либо тег пора снять.
Ответ на вопрос — три правки, и первая обязательна. Раздел «Вопросы»
опустошается: ответ переезжает в тело решением, а не остаётся вопросом рядом с
ответом. Затем снимается тег (edit <slug> --rm-tag question) и переписывается
«зачем»: «Решено: …» на вопрос «зачем нужна эта задача» уже не отвечает.
Порядок именно такой, потому что судит раздел, а не тег. ready
смотрит в непустой раздел и откажет даже при снятом теге, а check
на снятый тег при непустом разделе посоветует тег вернуть. Снять тег, не
опустошив раздел, — значит закольцевать себя между двумя советами.
Слаг
Латиница и цифры, kebab-case, без ведущих, хвостовых и двойных дефисов
(foo-bar, не -foo, a--b). Именуется по сути, а не по текущей
формулировке: заголовок будет переписан при переоценке, а слаг стоит в ссылках
из других задач, коммитов и черновиков. Транслита не заводим —
tie-break-equal-completeness, а не taj-brejk-pri-ravnoj-polnote: транслит
нечитаем для того, кто ищет по смыслу, и не сокращается.
Переименование слага — не правка, а перенос ссылок: делается одним атомарным проходом по всем местам, где слаг упомянут, иначе останутся битые ссылки, которых никто не проверяет.
Индекс
Строка одной формы:
- [🐞 Заголовок дословно](items/slug.md) — зачем
«Зачем» отвечает на «зачем нужна эта задача» одним предложением: состояние, остаток, боль. Пересказ первого абзаца бесполезен — он уже есть по ссылке. Эмодзи внутри квадратных скобок не украшение: заголовок копируется дословно, и тип виден там, где решают «брать или не брать».
| Файл | Что отвечает | Секции |
|---|---|---|
BACKLOG.md |
что можно взять, в значимом порядке | называет проект; на стройке ровно одна (умолчание План), на доработке сколько нужно (умолчание Ядро/Инфра) |
REJECTED.md |
что ушло без реализации и почему | — |
Секции — единственные заголовки ## в индексе: любой другой ## в
преамбуле проверка сочтёт секцией.
Порядок строк внутри секции значим, и стадия решает, что он значит: на
стройке зависимость, на доработке важность (SKILL.md, «Две стадии»). Назначает
его человек — раскладывая шаги или на груминге, — и двигают его move --after
и move --first. Одно место из очереди изъято и производно от типа и
заполненности: сырьё (research без раздела «Вопрос») стоит в конце своей
секции, потому что его не берут, и между берущимся оно каждый раз требует
открыть файл, чтобы это понять. Проверяет check, переставляет check --fix,
и человек этот порядок не назначает — иначе он был бы решением, которого здесь
нет.
Секции «блокеры» среди них нет. Блокер — состояние, а не полка: он живёт до
ответа человека, а следы остаются вопросами в файлах задач.
Постоянно пустая секция со старой семантикой «разбираются пачками» противоречила
бы правилу «эскалируем немедленно», поэтому init её не заводит, а check
говорит о ней в чужом беклоге. Переезжаешь с такой секцией — удали её.
Имена секций проект выбирает сам, а количество ограничено стадией: на
стройке секция одна, потому что порядок там зависимость, и разложенный по полкам
список перестаёт быть планом. Проверяет check; слить секции сам он не берётся —
в каком порядке пойдут строки слитых полок, знает только человек.
Заголовок секции пишется с прописной и отбивается пустой строкой с обеих
сторон. Отбивку правит check --fix; он же сводит написание места в мете файла
с заголовком индекса.
Индекс производен: расходится с файлом — правим индекс (check --fix).
Строку руками не пишут.
Отсюда же ответ на «а если оборвётся посередине». Мутация сперва проверяет всё
и складывает правки, и только потом пишет: сначала все временные файлы, потом
переименования подряд. Полной транзакции на несколько файлов файловая система не
даёт, но окно сжато до цепочки переименований, а всё, что в нём может
разъехаться, — производное: файлы целы, индекс восстанавливает check --fix.
Поэтому отказ на второй задаче из пяти не оставляет первую переписанной при
нетронутом индексе.
REJECTED.md
Туда уходит задача, покинувшая беклог без реализации. Строку пишет
tasks.py close --reason, а check следит за форматом:
- 2026-07-23 `versii-kachestvo-repaki` — Версии и качество одного тайтла.
Причина: калибровка болей — не боль, ни разу не возникло за полгода.
Была секция: Инфра.
Реализованные сюда не попадают: у них остаётся коммит и документация. У выкинутой не остаётся ничего — и через квартал она возвращается тем же текстом. Это первое место, куда смотрит дедупликация при заведении.
Запись не запрещает завести задачу заново: изменился контекст — заводим и ссылаемся на строку, объясняя, что изменилось.
Теги
Разметка сверх типа. Тип полем, потому что он один и обязателен; теги — потому
что их много и list --tag уже умеет отбирать по ним порцию разбора.
question— в файле есть неразобранный раздел «Вопросы».
Тегов kind:<род>, goal:<слаг> и decomposed больше нет: род работы стал
типом, а цели упразднены. Оставшиеся в файле check называет дрейфом, а check --fix снимает (значение kind: при этом переезжает в поле «Тип»).
Отбор — list --tag a,b: перечисленные через запятую теги требуются все
сразу (это И, не ИЛИ). Тег, которого нет ни у одной задачи, list называет
вслух: молчаливый ноль читается как «таких задач нет», а чаще это опечатка.
Свои теги проект заводит свободно (партия ревью review-ГГГГ-ММ-ДД, тема,
источник) — словарь не фиксирован. В индекс теги не выносим: он
производен, отбор делает list --tag, а не глаза.
Тест «готова к взятию»
Задача готова, если из файла отвечаются три вопроса. Первый общий, второй и третий у каждого типа свои и перечислены в его файле.
- Что станет наблюдаемо иначе, когда она сделана — снаружи: пользователю,
владельцу сервиса или разработчику. «Отрефакторить X» — не ответ; «перестанет
ломаться Y при Z» — ответ. У
choreадресат — разработчик, и это законно: «уедет последний вызов устаревшего API» — ответ, а не отговорка. Тип объявлен как раз затем, чтобы такие задачи не выдумывали себе пользовательскую пользу. - Что известно про сегодня — то, что тип требует знать до работы:
у
fixэтоВоспроизведение, уresearch—Вопрос, уfeatureиchore—Затрагивает. - По чему видно, что закончено — критерии приёмки с оракулами;
у
researchвместо нихКуда ляжет ответ. Не отвечается любой из трёх → это ещё не задача, а сырьё: типresearchбез раздела «Вопрос», место — конец секции, работа над ним — штурм.
Отвечается всё, но задача не делается одним заходом и не мерджится целиком →
это несколько задач, дроби сразу и ставь их в списке подряд. Промежуточного
зонтика между планом и задачей нет: тип [epic] упразднён, и цель, ставшая
зонтиком после него, упразднена тоже.
Тест применяется при заведении и при переоценке. К старым задачам, которых операция не касается, задним числом не применяется — беклог не переоформляют «заодно».