av-dev-pm: плагин переименован, заведены канон документов и скиллы init/canon/docs
- av-dev-tasks → av-dev-pm; канон определён единственным reference-файлом, который читают все три новых скилла - canon: check/adopt/upgrade плюс docs.py — раскладка, битые ссылки, версия, маркеры долга, сверки миграций и capability с документацией - tasks и session: путь docs/tasks жёсткий, конфиг переехал в docs/.pm.json, слот «Команда учёта задач» убран в пользу вызова скилла, раздел «Стимулы» переписан под совпавших приёмщика и исполнителя
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
# Адаптация каталога задач
|
||||
|
||||
Проект, где задачи уже как-то ведутся, и из имеющегося материала **выводится**
|
||||
заполненный каталог задач: цели, задачи, кладбище, индексы. Операция разовая —
|
||||
после неё проект живёт скиллами `tasks` и `session`.
|
||||
|
||||
**Это часть приведения проекта к канону.** Раскладку `docs/` целиком ведёт скилл
|
||||
`av-dev-pm:canon`; он же зовёт этот сценарий на шаге «каталог задач», потому что
|
||||
форматом задач владеет `tasks`, а не `canon`. Отдельно сценарий вызывается,
|
||||
когда переводить надо **только** задачи.
|
||||
|
||||
Вход какой угодно: старая раскладка `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. **Осмотрись.** Где лежат задачи, план, заметки. Каталог задач по канону —
|
||||
всегда `docs/tasks`. Секции беклога (`--sections`) — по умолчанию
|
||||
`ядро,инфра`; если у проекта деление другое по существу, оно называется
|
||||
здесь, а не подгоняется под умолчание, и уезжает в `docs/.pm.json`.
|
||||
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` — результат строкой.
|
||||
@@ -0,0 +1,102 @@
|
||||
# Задачи из аудита и ревью
|
||||
|
||||
Ревью и аудиты — код-ревью, архитектурный проход, аудит безопасности, любой
|
||||
разбор другим агентом — порождают находки, часть которых становится задачами.
|
||||
Это отдельный интейк со своей опасностью, **зеркальной** интейку из диалога.
|
||||
|
||||
- Интейк из диалога грешит переполнением: из одной мысли рождается пять файлов.
|
||||
- Интейк из ревью грешит сваливанием: сорок сырых находок превращаются в сорок
|
||||
файлов. Беклог раздувается, а следующая переоценка склеивает их обратно.
|
||||
|
||||
Защита от сваливания — та же, что в самом ревью: **кластеризация по причине, а
|
||||
не файл-на-находку.** Если у ревью был триаж — половина работы уже сделана, бери
|
||||
его выход. Если нет — триажируй сам, прежде чем заводить.
|
||||
|
||||
## Находка агента — не задача
|
||||
|
||||
Мнение агента — **гипотеза, пока у неё нет свидетельства** (падающий тест,
|
||||
воспроизводимый шаг, положение гайда). Согласие нескольких находок само по себе
|
||||
достоверность не повышает: это один источник, высказавшийся несколько раз.
|
||||
|
||||
Отсюда фильтр входа, поверх обычного «не делаем сейчас + пожалеем о потере»:
|
||||
|
||||
- **Находка со свидетельством**, отложенная к исполнению → **задача**.
|
||||
Свидетельство и последствие переносим в тело — это её «почему», то самое, что
|
||||
переживает запись.
|
||||
- **Находка без свидетельства / низкой уверенности** → **идея** (`[idea]`), а не
|
||||
задача. Её судьба — штурм, где либо найдётся подтверждение, либо она уедет в
|
||||
`REJECTED.md`.
|
||||
- **Уже починено по ходу ревью** → **ничего**. Починенное не заводим.
|
||||
- **Развилка, решённая при ревью** → ничего; решённая «потом» → задача с
|
||||
вопросом в разделе «Вопросы» и тегом `question`.
|
||||
|
||||
## Порядок
|
||||
|
||||
1. **Возьми выход триажа, а не сырые находки.** Сырой отчёт — это симптомы до
|
||||
дедупликации; в нём одна причина размазана по нескольким строкам.
|
||||
2. **Кластеризуй по причине.** Пять находок об одном отсутствующем инварианте —
|
||||
одна задача, а не пять. Класс мелочи (nits, косметика) — **один пакетный
|
||||
файл** со списком пунктов, а не файл на каждую запятую.
|
||||
3. **Дедуп против живых задач и `REJECTED.md`.** Аудит переоткрывает уже
|
||||
заведённое и уже выкинутое. Нашлось среди живых — дописываем находку в
|
||||
существующий файл. Нашлось в `REJECTED.md` — это сигнал: причина отказа могла
|
||||
устареть, выноси пользователю, а не заводи молча заново.
|
||||
4. **Разложи по целям.** У каждой заводимой задачи должен быть `goal:<слаг>`.
|
||||
Половина находок ревью не служит ничему из линии продукта — их цель это
|
||||
**куст** («прочность слияния», «журнал и пересборка», «наблюдаемость»).
|
||||
Подходящего куста нет — заведи его целью (`add --type goal --section кусты`)
|
||||
в том же проходе: без цели задача не попадёт ни в один спринт, а значит не
|
||||
будет сделана никогда.
|
||||
5. **Покажи карту до создания файлов.** Кластер → задача / идея / строка в
|
||||
пакетный файл / уже заведено / отброшено, и под какую цель — пачкой через
|
||||
`AskUserQuestion`. Это тот же барьер, что и «три кандидата» в интейке из
|
||||
диалога: массовое заведение файлов без подтверждения — ровно тот отказ, ради
|
||||
которого интейк из ревью и выделен. Дешёвая мелочь по явному согласию может
|
||||
заводиться и без поштучного вопроса — но карта пользователю предъявляется
|
||||
всё равно.
|
||||
6. **Заводи утверждённое** через `tasks.py add`, с двумя добавками:
|
||||
- **тег партии** — `--tag review-ГГГГ-ММ-ДД` (или `audit-<тема>`), чтобы весь
|
||||
заход разбора поднимался одной командой `list --tag …`;
|
||||
- **провенанс в теле** — кто нашёл, каким проходом, с каким свидетельством.
|
||||
Без него через месяц не отличить проверенную находку от догадки.
|
||||
7. `tasks.py check`.
|
||||
|
||||
## Куда девается серьёзность, если приоритетов нет
|
||||
|
||||
Приоритетов нет, и отображать серьёзность некуда — но **выкидывать её нельзя**.
|
||||
Правило замены:
|
||||
|
||||
- **тяжёлая находка со свидетельством** → задача под ту цель, которой она
|
||||
угрожает, и **кандидат в ближайший набор**: серьёзность здесь превращается в
|
||||
довод при выборе цели следующего спринта, а не в уровень в файле. Довод
|
||||
записывается причиной в мета-строке (`--reason`), иначе к моменту набора его
|
||||
никто не вспомнит;
|
||||
- **находка, ломающая уже идущий спринт**, — не интейк вовсе: см. правило
|
||||
вторжения в скилле `session`. В беклог она падает, только если врываться не
|
||||
положено;
|
||||
- **низкая уверенность или нет свидетельства** → идея;
|
||||
- **мелочь** → строка в пакетный файл;
|
||||
- **уже починено / развилка решена сейчас** → ничего.
|
||||
|
||||
Словарей серьёзности много, и отображать их механически не на что: при сомнении
|
||||
— вопрос пользователю, а не догадка.
|
||||
|
||||
## Поимённая сверка
|
||||
|
||||
Интейк считается выполненным, только если **каждая** находка триажа получила
|
||||
исход: слаг заведённой задачи, ссылку на существующую, строку пакетного файла
|
||||
или запись «не заведена: причина». Нулевой урожай при непустом отчёте триажа
|
||||
виден сразу — и это единственный способ отличить «находок не было» от «не стал
|
||||
заводить». Список составляет не тот, кто отчитывается о заведении.
|
||||
|
||||
Границы покрытия отчёта — то, что ревью проверить **не смогло**, — не находки и
|
||||
в задачи не идут: у них нет предмета. Их место в докладе, не в беклоге.
|
||||
|
||||
## Доклад
|
||||
|
||||
- Источник (какое ревью/аудит, сколько находок на входе).
|
||||
- Свёрнуто в задачи: N кластеров из M находок, со слагами, целями и тегом партии.
|
||||
- Что не заведено и почему: починено инлайн, уже заведено, ушло в идеи, в
|
||||
`REJECTED.md`.
|
||||
- Поимённая сверка: находок на входе N, исход есть у N.
|
||||
- `tasks.py check`.
|
||||
@@ -0,0 +1,82 @@
|
||||
# Декомпозиция и мозговой штурм
|
||||
|
||||
Обе операции превращают одну запись в несколько (или в ноль). Разница во входе:
|
||||
декомпозиция дробит **готовую задачу или эпик**, штурм прорабатывает **идею**,
|
||||
которая ещё не задача.
|
||||
|
||||
## Тест декомпозиции
|
||||
|
||||
Задачу можно дробить, только если части удовлетворяют **обоим** условиям:
|
||||
|
||||
1. **Мерджатся независимо.** Часть Б не требует, чтобы часть А была уже влита.
|
||||
Есть порядок «сперва А, потом Б, иначе не собрать» → это не декомпозиция, а
|
||||
план реализации: шаги остаются **внутри одного файла**.
|
||||
2. **Каждая даёт видимую пользу.** Часть, полезная только в комплекте с другой,
|
||||
— не самостоятельная задача. Пользу проверяй тестом «готова к взятию»
|
||||
(task-format): что станет наблюдаемо иначе именно от этой части и какие у неё
|
||||
собственные критерии приёмки.
|
||||
|
||||
Не проходит хотя бы одно — **не дроби**. Ложная декомпозиция плодит файлы,
|
||||
которые нельзя взять поодиночке, и переоценка потом склеивает их обратно.
|
||||
|
||||
**Цель наследуется.** Все части несут `goal:` родителя: декомпозиция не меняет
|
||||
того, чему работа служит. Если у части цель другая — это признак, что дробили не
|
||||
по той границе, либо что часть вообще из другой работы.
|
||||
|
||||
## Что делать с родителем
|
||||
|
||||
После разделения родитель **не остаётся** третьей висящей строкой:
|
||||
|
||||
- части полностью замещают его → `close <slug> --reason "разложена на a, b"`.
|
||||
`REJECTED.md` здесь — не «выкинули», а именно тот след, что переживает запись:
|
||||
через квартал вопрос «куда делась задача X» отвечается строкой со ссылками на
|
||||
наследников, а не археологией git;
|
||||
- родитель осмыслен как зонтик → `edit <slug> --type epic`, тело — ссылки на
|
||||
задачи-части, своих шагов у него нет. **Эпик не берётся в спринт** и живёт
|
||||
ровно до тех пор, пока не закрыта последняя часть.
|
||||
|
||||
Зонтик, который перестал быть временным и описывает направление, а не работу, —
|
||||
это уже **цель**, а не эпик. Тип на месте не меняется (цель живёт в другом
|
||||
индексе): заводится `[goal]` в `PLAN.md`, задачи получают `--goal <новый слаг>`,
|
||||
эпик закрывается с причиной-ссылкой.
|
||||
|
||||
## Когда декомпозиция случается посреди спринта
|
||||
|
||||
Задача, которая **переросла в эпик**, распознаётся до того, как под неё заведено
|
||||
предложение об изменении: иначе его придётся выбрасывать. Она помечается
|
||||
`[epic]`, выходит из набора (`sprint drop … --reason "переросла в эпик"`), уходит
|
||||
на декомпозицию, а спринт продолжается остальными. Части заводятся сразу, но в
|
||||
текущий набор **не добавляются** — набор заморожен.
|
||||
|
||||
## Мозговой штурм идеи
|
||||
|
||||
Идея (`[idea]`) не проходит тест «готова к взятию»: неясно, что именно делаем.
|
||||
Штурм проясняет — и это **generative-операция, а не applicative**.
|
||||
|
||||
Applicative-штурм («перечисли задачи, следующие из идеи») выдаёт очевидное:
|
||||
перечисляется то, что уже видно в формулировке. Ценное — на уровень выше.
|
||||
|
||||
1. **Сперва — формы, а не задачи.** Предложи **три разные постановки** идеи и
|
||||
назови **компромисс каждой**: что она даёт, чем платит, что оставляет за
|
||||
бортом. Если получилась одна постановка — штурм не состоялся, это
|
||||
applicative.
|
||||
2. **Вынеси формы пользователю** через `AskUserQuestion` с компромиссами. Рамку
|
||||
выбирает он: это продуктовое решение, не механика.
|
||||
3. **Назови цель.** Выбранная форма служит какой-то цели — существующей или
|
||||
новой. Идея, для которой цель не находится, скорее всего уезжает в
|
||||
`REJECTED.md`, а не заводится задачей.
|
||||
4. **Только выбранную форму** дроби по тесту декомпозиции выше и проставь
|
||||
критерии приёмки: без них наследники останутся идеями под другим именем.
|
||||
|
||||
**«Выкинуть» — полноправный исход штурма, а не его неудача.** Проработка, честно
|
||||
показавшая, что пользы нет или она несоразмерна цене, — это результат: идея
|
||||
уезжает с этой самой причиной, и та причина гасит её повторное появление.
|
||||
|
||||
## Доклад
|
||||
|
||||
- Идея/задача на входе, выбранная рамка (для штурма), задачи-наследники со
|
||||
слагами, целями и секциями.
|
||||
- Судьба родителя: удалён / стал эпиком / стал целью / выкинут с причиной.
|
||||
- `tasks.py check` после правок.
|
||||
- Границы покрытия: какие постановки рассмотрены и какие сознательно отброшены —
|
||||
чтобы штурм не пришлось повторять с нуля.
|
||||
@@ -0,0 +1,245 @@
|
||||
# Формат задач, целей и индексов
|
||||
|
||||
Заголовок, мета-строку и строку индекса ставит `tasks.py add` — руками их не
|
||||
пишут. Этот файл описывает, что именно скрипт создаёт и что проверяет `check`;
|
||||
тело задачи (одну фразу, критерии, рамки, контекст) дописывает агент.
|
||||
|
||||
## Файл задачи
|
||||
|
||||
`items/<slug>.md`:
|
||||
|
||||
```markdown
|
||||
# Тай-брейк при равной полноте
|
||||
|
||||
**Секция:** ядро — вышла из спринта: остаток писал нерешённое в журнал · **Хук:** порядок канонических форм берёт меньшее в 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`, а хук
|
||||
переписывается: «Решено: …» на вопрос «почему это лежит в беклоге» уже не
|
||||
отвечает.
|
||||
|
||||
## Файл цели
|
||||
|
||||
```markdown
|
||||
# [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`: транслит
|
||||
нечитаем для того, кто ищет по смыслу, и не сокращается.
|
||||
|
||||
Переименование слага — не правка, а перенос ссылок: делается одним атомарным
|
||||
проходом по всем местам, где слаг упомянут, иначе останутся битые ссылки,
|
||||
которых никто не проверяет.
|
||||
|
||||
## Индексы
|
||||
|
||||
Строка везде одной формы:
|
||||
|
||||
```markdown
|
||||
- [Заголовок дословно](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` следит за форматом:
|
||||
|
||||
```markdown
|
||||
- 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`, а не глаза.
|
||||
|
||||
## Тест «готова к взятию»
|
||||
|
||||
Задача готова, если из файла отвечаются три вопроса:
|
||||
|
||||
1. **Что станет наблюдаемо иначе**, когда она сделана — снаружи: пользователю,
|
||||
владельцу сервиса или разработчику. «Отрефакторить X» — не ответ; «перестанет
|
||||
ломаться Y при Z» — ответ.
|
||||
2. **По чему видно, что закончено** — критерии приёмки с оракулами.
|
||||
3. **Какой цели она служит** — тег `goal:` и одна строка «почему именно этой».
|
||||
|
||||
Не отвечается первый или второй вопрос → это **идея** (`[idea]`), её место в
|
||||
штурме. Не отвечается третий → либо цель есть и не проставлена, либо задача не
|
||||
служит ничему — тогда её не надо заводить.
|
||||
|
||||
Отвечается всё, но задача не делается одним заходом и не мерджится целиком →
|
||||
**эпик** (`[epic]`), сперва декомпозиция. Эпик временен и исчезает после
|
||||
разбора; цель (`[goal]`) постоянна — не путать.
|
||||
|
||||
Тест применяется при заведении и при переоценке. К старым задачам, которых
|
||||
операция не касается, задним числом не применяется — беклог не переоформляют
|
||||
«заодно».
|
||||
Reference in New Issue
Block a user