приоритет стал исполнимым: move без --section и тексты, догнавшие правило 4
Механизм расстановки приоритета не запускался ни разу: --section у move был обязательным, а все три места, где груминг его предписывает, дают команду без него — usage error. Чиню скриптом, а не текстами: перестановка внутри секции — самая частая операция груминга, и требовать повторить текущую секцию значит приглашать указать не ту. - move: --section необязателен, без него берётся секция из индекса; сообщение различает перестановку и перенос - докстринги, отрицавшие правило 4 («в беклоге порядок значения не имеет»), приведены к действительности - reopen ставил возвращённую строку после сырья и давал ошибку check на ровном месте - edit портил написание секции в мете; корень шире — брался нижний регистр из разбора, а не написание заголовка. То же в close и reopen - дыра гейта: между заведением и ready запись не судил никто. Своя строка здоровья check, отдельная от «готово к взятию» — она про другое - шесть файлов и два устава обещали, что схему типа проверяет check - раздел from-review о серьёзности стоял на «приоритетов нет» - остатки спринта и сессии в семи местах, включая description агента формы - индексов два, а не три; два определения порога готовности после adopt Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: task-form
|
||||
description: "Проверка формы записи каталога задач по существу: тип, разошедшийся с содержанием записи, форма заголовка по типу (цель — что приложение будет уметь, задача — что нужно сделать, разведка — о чём она), «зачем», пересказывающее заголовок вместо состояния и боли, раздел «Затрагивает» с замыслом вместо границ, критерий приёмки с оракулом только на словах, предписание процесса в теле, и связь задачи со строкой «Завершения» её цели. Читает файл цели, на которую ссылается задача. Отдаёт готовые формулировки на замену и ничего не правит сам. Язык текста (залог, оценки, стоп-слова, англицизмы) смотрит отдельный агент task-wording. Использовать после заведения или разбора пачки записей, до взятия в спринт и на переоценке. Только чтение."
|
||||
description: "Проверка формы записи каталога задач по существу: тип, разошедшийся с содержанием записи, форма заголовка по типу (цель — что приложение будет уметь, задача — что нужно сделать, разведка — о чём она), «зачем», пересказывающее заголовок вместо состояния и боли, раздел «Затрагивает» с замыслом вместо границ, критерий приёмки с оракулом только на словах, предписание процесса в теле, и связь задачи со строкой «Завершения» её цели. Читает файл цели, на которую ссылается задача. Отдаёт готовые формулировки на замену и ничего не правит сам. Язык текста (залог, оценки, стоп-слова, англицизмы) смотрит отдельный агент task-wording. Использовать после заведения или разбора пачки записей, при проверке готовности перед взятием в работу (tasks.py ready) и на груминге. Только чтение."
|
||||
tools: Read, Grep, Glob
|
||||
model: sonnet
|
||||
color: green
|
||||
@@ -130,12 +130,17 @@ color: green
|
||||
если ты открыл цель и увидел расхождение в самом документе, оно их. Увидел —
|
||||
назови в конце одной строкой, чтобы находка не пропала, но находкой не оформляй.
|
||||
|
||||
**Машинной проверке — вообще ничего.** Всё, что ловит `tasks.py check` (наличие
|
||||
разделов, число критериев, состав и написание секций, теги, тег `question` при
|
||||
непустом разделе «Вопросы», согласованность индексов, битые ссылки, форма
|
||||
заголовка как строки), **не пиши даже строкой**: это не потерянная находка, а
|
||||
уже проверенное. Повторять машинную проверку словами — заводить второй дом для
|
||||
одного правила.
|
||||
**Машинной проверке — вообще ничего.** Всё, что ловит `tasks.py check` (состав и
|
||||
написание секций, теги, тег `question` при непустом разделе «Вопросы»,
|
||||
согласованность индексов, битые ссылки, форма заголовка как строки), **не пиши
|
||||
даже строкой**: это не потерянная находка, а уже проверенное. Повторять машинную
|
||||
проверку словами — заводить второй дом для одного правила.
|
||||
|
||||
**Наличие разделов и число критериев `check` поимённо не называет** — он считает
|
||||
их строкой здоровья, а поимённо судит `tasks.py ready` на входе в работу.
|
||||
Отсутствующий раздел сам по себе всё равно не твоя находка (её увидит `ready`);
|
||||
твоя — раздел, который **есть и лжёт**: границы вместо замысла, критерий с
|
||||
оракулом только на словах.
|
||||
|
||||
**Содержание работы**: нужна ли задача, верно ли выбрана цель, не крупна ли она,
|
||||
достаточна ли декомпозиция. Седьмое правило подходит к этому близко и
|
||||
|
||||
@@ -191,11 +191,12 @@ color: green
|
||||
но находкой не оформляй.
|
||||
|
||||
**Машинной проверке — вообще ничего.** Всё, что ловит `tasks.py check` (состав и
|
||||
написание секций, наличие разделов своего типа, число критериев, теги, тег
|
||||
`question` при непустом разделе «Вопросы», согласованность файлов с индексами,
|
||||
битые ссылки), **не пиши даже строкой**: это не потерянная находка, а уже
|
||||
проверенное. Повторять машинную проверку словами — заводить второй дом для
|
||||
одного правила.
|
||||
написание секций, теги, тег `question` при непустом разделе «Вопросы»,
|
||||
согласованность файлов с индексами, битые ссылки), **не пиши даже строкой**: это
|
||||
не потерянная находка, а уже проверенное. Повторять машинную проверку словами —
|
||||
заводить второй дом для одного правила. Наличие разделов своего типа и число
|
||||
критериев `check` только считает — поимённо их судит `tasks.py ready`, и это
|
||||
тоже не твоя находка: твоя — язык того, что уже написано.
|
||||
|
||||
**Содержание работы**: нужна ли задача, верно ли выбрана цель, не крупна ли она,
|
||||
достаточна ли декомпозиция. Это разбор, и его ведёт человек со скиллом `tasks`.
|
||||
|
||||
@@ -112,10 +112,11 @@
|
||||
|
||||
Задача из верхних строк `--stale`, которую и этот заход оставляет без изменений,
|
||||
**либо двигается (меняет цель, поднимается в очереди, уходит с причиной), либо
|
||||
остаётся с явно записанной причиной**, почему её держим (`move <slug> --section
|
||||
<та же> --reason …`). Молчаливое «оставить как есть» на давно неподвижной задаче
|
||||
— это решение не принимать решение; запись причины превращает его в осознанное и
|
||||
не даёт тому же вопросу всплыть на следующем груминге.
|
||||
остаётся с явно записанной причиной**, почему её держим (`move <slug> --reason
|
||||
…` — без `--section` секция берётся текущая). Молчаливое «оставить как есть» на
|
||||
давно неподвижной задаче — это решение не принимать решение; запись причины
|
||||
превращает его в осознанное и не даёт тому же вопросу всплыть на следующем
|
||||
груминге.
|
||||
|
||||
## Шаг 4. Что важно сейчас — расстановка
|
||||
|
||||
|
||||
@@ -383,7 +383,7 @@ python3 $tk check --dir D --fix # + починить дрейф (тип
|
||||
python3 $tk list --dir D [--stale] [--section S] [--type T] [--tag a,b] [--goal S] [--raw] [--index …] [--questions]
|
||||
python3 $tk add --dir D --slug S --title T --type goal|feature|fix|chore|research [--section S] [--goal G] [--why «зачем»] [--tag a,b]
|
||||
python3 $tk edit S --dir D [--title T] [--why «зачем»] [--type T] [--goal G] [--add-tag a,b] [--rm-tag c]
|
||||
python3 $tk move S --dir D --section S [--reason R] [--after S | --first]
|
||||
python3 $tk move S --dir D [--section S] [--reason R] [--after S | --first] # без --section — текущая секция
|
||||
python3 $tk close S --dir D --reason R # в REJECTED.md + удалить (ушла без реализации)
|
||||
python3 $tk close S --dir D --implemented # просто удалить (реализована и закоммичена)
|
||||
python3 $tk reopen S --dir D --reason R # вернуть закрытую: приёмка не сошлась
|
||||
@@ -459,8 +459,11 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
|
||||
проставляет человек — `edit <слаг> --type …`.
|
||||
|
||||
**Что механизировано, а что нет.** Схему типа проверяет `ready` на входе в
|
||||
работу — там, где по ней принимают решение; `check` о недостающем только
|
||||
напоминает счётчиком «готово к взятию». У каждой части своя глубина:
|
||||
работу — там, где по ней принимают решение; `check` поимённо о ней не говорит, а
|
||||
считает: строка здоровья **«схема типа не выполнена: N из M»** называет число и
|
||||
первые слаги, строка **«готово к взятию»** — сколько задач беклога пройдут
|
||||
`ready` целиком (схема плюс цель плюс отсутствие открытого вопроса). Это две
|
||||
разные строки, и совпадение их чисел — совпадение. У каждой части своя глубина:
|
||||
|
||||
- **тип** — жёстко: назван и из закрытого словаря;
|
||||
- **критерии приёмки** (`feature`, `fix`, `chore`) — число пунктов жёстко
|
||||
@@ -605,7 +608,7 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
|
||||
мету файла и строку индекса заодно;
|
||||
- **вопрос, застрявший в прозе** — вынимается в раздел «Вопросы» плюс тег
|
||||
`question` (`edit --add-tag question`), иначе он не виден ни `list
|
||||
--questions`, ни правилу «задача с открытым вопросом в набор не берётся»;
|
||||
--questions`, ни правилу «задача с открытым вопросом в работу не берётся»;
|
||||
- **тег, который некому снять** — `question` после ответа снимается `edit
|
||||
--rm-tag question` вместе с записью ответа в тело **и опустошением раздела
|
||||
«Вопросы»**: судит раздел, а не тег (`references/task-format.md`);
|
||||
|
||||
@@ -94,8 +94,9 @@ python3 $tk adopt apply --plan tasks-adopt-plan.json \
|
||||
быть названо, иначе следующий агент примет пустой беклог за поломку.
|
||||
|
||||
`apply` печатает состояние по факту: сколько задач без цели (это **ошибки**
|
||||
`check`) и сколько без критериев (`check` их ошибкой не считает, но `ready`
|
||||
такую задачу не пропустит). Закрывается это **порциями груминга** — скилл
|
||||
`check`) и сколько не собрало разделы своего типа (для `check` это не ошибка, а
|
||||
строка здоровья, но `ready` такую задачу не пропустит). Закрывается это
|
||||
**порциями груминга** — скилл
|
||||
`groom`, 5–8 задач за порцию: проставить цели, превратить «готово, когда» в
|
||||
критерии с оракулами, вынуть вопросы из прозы в раздел «Вопросы». Там же
|
||||
беклогу впервые назначается **порядок**: после адаптации его нет вовсе, а
|
||||
|
||||
@@ -73,21 +73,31 @@
|
||||
Без него через месяц не отличить проверенную находку от догадки.
|
||||
7. `tasks.py check`.
|
||||
|
||||
## Куда девается серьёзность, если приоритетов нет
|
||||
## Куда девается серьёзность находки
|
||||
|
||||
Приоритетов нет, и отображать серьёзность некуда — но **выкидывать её нельзя**.
|
||||
Правило замены:
|
||||
Уровня серьёзности в записи нет — но **выкидывать её нельзя**: серьёзность
|
||||
отображается **в позицию в очереди**, потому что приоритет и есть порядок строк
|
||||
в беклоге (правило 4 [SKILL.md](../SKILL.md)). Отображается через довод, а не
|
||||
напрямую: своей шкалы у интейка нет, доводы расстановки перечислены в
|
||||
[скилле груминга](../../groom/SKILL.md#приоритет-как-его-расставляют), и
|
||||
серьёзность попадает ровно в один из них.
|
||||
|
||||
- **тяжёлая находка со свидетельством** → задача под ту цель, которой она
|
||||
угрожает, и **кандидат на верх очереди**: серьёзность здесь превращается в
|
||||
довод при расстановке приоритета, а не в уровень в файле. Довод записывается
|
||||
причиной в мете (`--reason`), иначе к моменту груминга его никто не вспомнит.
|
||||
Саму строку интейк ставит в конец секции: очередь назначает человек;
|
||||
- **находка, которая не ждёт груминга вовсе** (необратимый ущерб, сломан общий
|
||||
станок), — не интейк: это работа прямо сейчас, а в беклог она падает, только
|
||||
если ждать всё-таки можно;
|
||||
- **тяжёлая находка со свидетельством о сломанном сейчас** → задача под ту цель,
|
||||
которой она угрожает, и **первой строкой секции**: `move <слаг> --first
|
||||
--reason «сломано сейчас: …»`. Это довод «что сломано сейчас» из перечня
|
||||
груминга — единственный, который не требует сравнения с соседями по очереди,
|
||||
потому что сломанное дорожает само. Позицию всё равно назначает человек, и
|
||||
здесь он её уже назначил: верх очереди для такой находки предъявляется картой
|
||||
шага 5, а не проставляется молча;
|
||||
- **тяжёлая находка о риске, а не о поломке** (дорожает от ожидания,
|
||||
разблокирует остальное) → в конец секции, а довод — причиной в мете
|
||||
(`--reason`). Позицию назначит человек на ближайшем груминге, сравнив её с
|
||||
верхом очереди; без записанного довода сравнивать он будет с нуля;
|
||||
- **находка, которая не ждёт груминга вовсе** (необратимый ущерб, покраснела
|
||||
проверка, которую проект назвал сломанным), — не интейк: это работа прямо
|
||||
сейчас, а в беклог она падает, только если ждать всё-таки можно;
|
||||
- **низкая уверенность или нет свидетельства** → сырьё (`research` с пустым
|
||||
разделом «Вопрос»);
|
||||
разделом «Вопрос»): его место в очереди производно от типа — конец секции;
|
||||
- **мелочь** → строка в пакетный файл;
|
||||
- **уже починено / развилка решена сейчас** → ничего.
|
||||
|
||||
|
||||
@@ -57,9 +57,11 @@
|
||||
`REJECTED.md` здесь — не «выкинули», а именно тот след, что переживает запись:
|
||||
через квартал вопрос «куда делась задача X» отвечается строкой со ссылками на
|
||||
наследников, а не археологией git;
|
||||
- родитель осмыслен как **возможность**, а не как шаг → это цель. Тип на месте
|
||||
не меняется (цель живёт в другом индексе): заводится `--type goal` в `ROADMAP.md`,
|
||||
части получают `--goal <новый слаг>`, родитель закрывается с причиной-ссылкой.
|
||||
- родитель осмыслен как **возможность**, а не как шаг → это цель, и **строка
|
||||
переезжает**: `edit <slug> --type goal --section <часть роадмапа>` снимает её с
|
||||
`BACKLOG.md` и вставляет в `ROADMAP.md`. Файл в `items/` при этом не двигается —
|
||||
он и есть запись. Части получают `--goal <слаг родителя>`, а закрывать родителя
|
||||
нечем и незачем: он не выкинут, он стал целью.
|
||||
|
||||
**Промежуточного зонтика между целью и задачей нет.** Тип `epic` упразднён:
|
||||
роль зонтика играет цель, а слишком крупный шаг дробится на шаги помельче под
|
||||
@@ -70,7 +72,7 @@
|
||||
|
||||
Задача, которая **оказалась крупнее задачи**, распознаётся до того, как под неё
|
||||
заведено предложение об изменении: иначе его придётся выбрасывать. Она выходит
|
||||
уходит на декомпозицию, а её строка возвращается в беклог с причиной
|
||||
из работы на декомпозицию, а её строка возвращается в беклог с причиной
|
||||
(`move … --reason "крупнее задачи"`). Части заводятся сразу под той же целью, и
|
||||
**место в очереди им назначает человек**: машина поставит их в конец секции, а
|
||||
крупная задача редко распадается на что-то менее срочное, чем была сама.
|
||||
|
||||
@@ -48,13 +48,13 @@
|
||||
5. **Проверить, что это не «заодно».** Обслуживание любит склеиваться в пачку
|
||||
(«обновить зависимости и переписать сборку и убрать мёртвый код»). Не
|
||||
мерджится порознь — это несколько задач ([split.md](split.md)).
|
||||
6. **Цель не проставлять.** `chore` служит работоспособности, а не направлению,
|
||||
работоспособности, а не направлению. Работа по сопровождению проекта
|
||||
при этом видна в роадмапе — секцией `Сопровождение`, но целью не становится.
|
||||
6. **Цель не проставлять.** `chore` служит работоспособности, а не направлению.
|
||||
Работа по сопровождению проекта при этом видна в роадмапе — секцией
|
||||
`Сопровождение`, но целью не становится.
|
||||
|
||||
## Что видит машина, а что человек
|
||||
|
||||
`check` и `ready` смотрят на **наличие непустого** `Затрагивает` и на
|
||||
`ready` смотрит на **наличие непустого** `Затрагивает` и на
|
||||
**число** критериев — ровно то же, что у `feature`. Разница между типами здесь не
|
||||
в строгости проверки, а в том, **кому адресован ответ** на «что станет
|
||||
наблюдаемо иначе», — и это судит человек.
|
||||
|
||||
@@ -48,9 +48,11 @@
|
||||
|
||||
## Что видит машина, а что человек
|
||||
|
||||
`check` и `ready` смотрят на **наличие непустого** раздела `Затрагивает`,
|
||||
на **число** критериев (меньше двух — отказ, больше пяти — замечание) и на цель.
|
||||
Наличие оракула проверяется **эвристикой** — словом «оракул» в пункте.
|
||||
Схему типа судит `ready` на входе в работу: **наличие непустого** раздела
|
||||
`Затрагивает`, **число** критериев (меньше двух — отказ, больше пяти —
|
||||
замечание) и цель. Наличие оракула проверяется **эвристикой** — словом «оракул»
|
||||
в пункте. `check` этого поимённо не говорит, а считает строкой здоровья
|
||||
(`SKILL.md`, «Что механизировано, а что нет»).
|
||||
|
||||
Полнота перечня границ машине не видна: границу, которую забыли назвать, она от
|
||||
отсутствующей не отличает. Настоящий оракул от слова «оракул» тоже не отличает.
|
||||
|
||||
@@ -54,9 +54,8 @@
|
||||
почти всегда есть парный критерий: **прежнее поведение не сломалось**
|
||||
(«ввод `а1` принимается по-прежнему»). Без него починка чинит одно и ломает
|
||||
соседнее.
|
||||
6. **Цель не выдумывать.** `fix` служит работоспособности, а не направлению, и в
|
||||
работоспособности, а не направлению. Придуманная цель — то же враньё, от
|
||||
которого спасает тип.
|
||||
6. **Цель не выдумывать.** `fix` служит работоспособности, а не направлению.
|
||||
Придуманная цель — то же враньё, от которого спасает тип.
|
||||
7. **Записать дефект в журнал** `docs/review.md` с пометкой «проскочил / пойман
|
||||
ревью». Проскочившие — проверочный набор для калибровки конвейера; пойманные с
|
||||
оракулом — лучшая опора для прохода ревью: проектные, воспроизводимые,
|
||||
@@ -64,7 +63,7 @@
|
||||
|
||||
## Что видит машина, а что человек
|
||||
|
||||
`check` и `ready` смотрят на **наличие непустого** `Воспроизведения` и
|
||||
`ready` смотрит на **наличие непустого** `Воспроизведения` и
|
||||
`Затрагивает` и на **число** критериев. Годность воспроизведения — человеку:
|
||||
шаги, по которым ничего не воспроизводится, машина от годных не отличает, и
|
||||
делать вид, что проверено больше проверенного, хуже, чем не проверять вовсе.
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
имя таблицы стабильно, номер последней миграции протухает молча. Пишется
|
||||
`таблица points и её миграция`, а не `миграция 0042`.
|
||||
|
||||
**Что из этого механизировано.** `check` и `ready` смотрят только на
|
||||
**Что из этого механизировано.** `ready` смотрит только на
|
||||
**наличие непустого раздела**. Полнота перечня машине не видна: границу, которую
|
||||
забыли назвать, она от отсутствующей не отличает. Раздела нет — отказ во взятии:
|
||||
оценивать нечем.
|
||||
@@ -158,7 +158,7 @@
|
||||
конкретизация вопроса «по чему видно, что закончено» из теста готовности ниже:
|
||||
там сказано «признак завершённости», здесь — «признак плюс чем проверяется».
|
||||
|
||||
**Что из этого механизировано.** `check` и `ready` считают пункты: меньше
|
||||
**Что из этого механизировано.** `ready` считает пункты: меньше
|
||||
двух — отказ («— работает» одной строкой больше не проходит), больше пяти —
|
||||
замечание, обычно это признак, что задача крупнее задачи. Наличие оракула
|
||||
проверяется **эвристикой** — словом «оракул» в пункте, — и потому даёт только
|
||||
|
||||
@@ -75,9 +75,9 @@
|
||||
не попадает: `Готово` отвечает «что приложение умеет», а отменённая цель не
|
||||
умеет ничего.
|
||||
|
||||
**Место этому — переоценка на сессии, а не отдельный заход.** Отмена цели значит
|
||||
разбор всех её задач, а разбор задач и есть шаг 3 сессии
|
||||
(скилл `groom`, разбор «что перестало быть важным»). Отменять на ходу,
|
||||
**Место этому — груминг, а не отдельный заход.** Отмена цели значит
|
||||
разбор всех её задач, а разбор задач и есть шаг 3 груминга
|
||||
(скилл `groom`, «что перестало быть важным»). Отменять на ходу,
|
||||
между делом, — верный способ закрыть скопом то, что стоило перевесить.
|
||||
|
||||
## Что видит машина, а что человек
|
||||
|
||||
@@ -75,9 +75,9 @@
|
||||
|
||||
## Что видит машина, а что человек
|
||||
|
||||
`check` и `ready` смотрят на **наличие непустых** разделов `Вопрос` и
|
||||
`Куда ляжет ответ`, считают сырьё отдельной строкой здоровья и держат его в конце
|
||||
секции. Годность вопроса — человеку: «вопрос это или тема» машина не различает,
|
||||
`ready` смотрит на **наличие непустых** разделов `Вопрос` и
|
||||
`Куда ляжет ответ`; `check` считает сырьё отдельной строкой здоровья и держит
|
||||
его в конце секции. Годность вопроса — человеку: «вопрос это или тема» машина не различает,
|
||||
и `check` о годности молчит намеренно.
|
||||
|
||||
Штурм сырья, дробление исхода на задачи и тест «части мерджатся порознь» —
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
|
||||
Преемник backlog.py. Разница по существу одна: **секция-как-уровень заменена
|
||||
целью** (`goal:<слаг>` тегом), а приоритет стал тем, чем он и является, —
|
||||
**порядком строк в беклоге**. Индексов три, и задача живёт ровно в одном из них
|
||||
за раз.
|
||||
**порядком строк в беклоге**. Индексов два — беклог и роадмап, — и задача живёт
|
||||
ровно в одном из них за раз. `REJECTED.md` индексом не считается: он не говорит,
|
||||
где запись числится, он кладбище ушедшего.
|
||||
|
||||
Раскладка. Путь каталога — `tasks/` в корне репозитория, жёстко. Каталог
|
||||
принадлежит этому плагину, а не канону документов: `docs/` ведёт другой плагин, и
|
||||
@@ -66,7 +67,7 @@ goal | feature | fix | chore | research, по-английски, как и пр
|
||||
[--section S] [--goal G] [--why H] [--reason R] [--tag a,b] [--dir DIR]
|
||||
tasks.py edit S [--title T] [--why H] [--type T] [--goal G]
|
||||
[--add-tag a,b] [--rm-tag c,d] [--section S] [--dir DIR]
|
||||
tasks.py move S --section S [--reason R] [--after S | --first] [--dir DIR]
|
||||
tasks.py move S [--section S] [--reason R] [--after S | --first] [--dir DIR]
|
||||
tasks.py close S (--reason R | --implemented) [--dir DIR]
|
||||
tasks.py reopen S [--reason R] [--dir DIR]
|
||||
tasks.py ready S [S …] [--dir DIR]
|
||||
@@ -659,9 +660,10 @@ def raw_last(lines: list[str], raw: set[str]) -> list[str]:
|
||||
"""Строки индекса, у которых сырьё снесено в конец своей секции.
|
||||
|
||||
Сырьё (`research` без раздела «Вопрос») в работу не берётся, и стоя между
|
||||
берущимися оно каждый раз требует открыть файл, чтобы это понять. Порядка
|
||||
«по важности» в беклоге по-прежнему нет: этот порядок **производен от
|
||||
типа**, а не назначен человеком, — потому его и можно проверять машиной.
|
||||
берущимися оно каждый раз требует открыть файл, чтобы это понять. Порядок
|
||||
строк в беклоге — приоритет, и назначает его человек; место сырья —
|
||||
единственное исключение, и оно **производно от типа**, а не назначено, —
|
||||
потому его и можно проверять машиной.
|
||||
|
||||
Переставляются только сами строки-пункты, по своим же позициям: проза
|
||||
внутри секции, отбивка и заголовки остаются на месте.
|
||||
@@ -1265,6 +1267,26 @@ def health(lay: Layout, tasks: dict, entries: dict, sections: dict) -> None:
|
||||
else " — прочим не хватает разделов своего типа, цели или ждут"
|
||||
" ответа на вопрос"))
|
||||
|
||||
# Схема типа — **своя** строка, а не дубль предыдущей. «Готово к взятию»
|
||||
# считает только беклог и валит запись за что угодно (вопрос, отсутствие
|
||||
# цели, разделы); эта называет ровно одну причину и судит **все** записи,
|
||||
# включая цели, которых `ready` не смотрит вовсе. Строка нужна потому, что
|
||||
# обязательность разделов проверяет только `ready` на входе в работу, а
|
||||
# между заведением и взятием запись иначе не судит никто.
|
||||
bad_schema = {n: t for n, t in tasks.items()
|
||||
if t["type"] in TYPE_SCHEMA and schema_verdict(lay, t)[0]}
|
||||
if bad_schema:
|
||||
unfit = sorted(n[:-3] for n in bad_schema)
|
||||
# Сырьё названо отдельно: схему оно не выполняет по определению («Вопрос»
|
||||
# пуст — тем оно и сырьё), и без этой оговорки счётчик читался бы как
|
||||
# число недоделанных задач, хотя часть его — записи, ещё не ставшие ими.
|
||||
crude = sum(1 for t in bad_schema.values() if raw_research(lay, t))
|
||||
print(f" схема типа не выполнена: {len(unfit)} из {len(tasks)}"
|
||||
f" ({', '.join(unfit[:5])}{', …' if len(unfit) > 5 else ''})"
|
||||
+ (f", сырья из них {crude}" if crude else "")
|
||||
+ " — нет разделов, которых требует тип; чего именно, скажет"
|
||||
" `tasks.py ready <слаг>`")
|
||||
|
||||
questions = [n for n, t in tasks.items() if questions_open(lay, t)]
|
||||
if questions:
|
||||
print(f" с открытым вопросом: {len(questions)}"
|
||||
@@ -1483,6 +1505,15 @@ def find_section(lines: list[str], name: str) -> tuple[int | None, str]:
|
||||
return None, ""
|
||||
|
||||
|
||||
def section_at(lines: list[str], i: int) -> str:
|
||||
"""Секция, в которой лежит строка `i`, — ближайший заголовок выше неё.
|
||||
|
||||
Дом у этого вопроса один: его задаёт и перестановка внутри секции
|
||||
(`move` без `--section`), и починка «строка не в своей секции»."""
|
||||
return next((m.group(1) for j in range(i, -1, -1)
|
||||
if (m := SECTION.match(lines[j]))), "")
|
||||
|
||||
|
||||
def find_entry_index(lines: list[str], slug: str) -> int | None:
|
||||
for i, line in enumerate(lines):
|
||||
m = INDEX_ENTRY.match(line)
|
||||
@@ -1507,8 +1538,9 @@ def locate_all(lay: Layout, slug: str) -> dict[str, tuple[list[str], int]]:
|
||||
def insert_entry(lines: list[str], section: str, entry: str,
|
||||
after: str | None = None, first: bool = False) -> None:
|
||||
"""Вставляет строку в секцию: по умолчанию в конец, --after <слаг> — следом
|
||||
за указанной строкой, --first — первой. Порядок нужен только упорядоченной
|
||||
части роадмапа; в беклоге он значения не имеет."""
|
||||
за указанной строкой, --first — первой. Позиция значима прежде всего в
|
||||
беклоге: порядок строк там и есть приоритет (правило 4), и назначает его
|
||||
человек — отсюда и умолчание «в конец», а не «наверх»."""
|
||||
hi, _ = find_section(lines, section)
|
||||
if hi is None:
|
||||
raise Usage(f"секции «{section}» в индексе нет")
|
||||
@@ -1774,13 +1806,18 @@ def cmd_edit(lay: Layout, a: argparse.Namespace) -> int:
|
||||
tags = [t for t in tags if not t.startswith(LEGACY_KIND_TAG)]
|
||||
|
||||
why = task["why"] if a.why is None else a.why
|
||||
section = task["section"]
|
||||
# Написание секции берётся как есть, а не в нижнем регистре: имя секции
|
||||
# принадлежит **заголовку индекса**, и мета на него только ссылается (тот же
|
||||
# довод, что у шага 6 `apply_fixes`). Нижний регистр уезжал бы в файл
|
||||
# «Категория: ядро», а следующий `check --fix` чинил бы за собственной
|
||||
# правкой. Сверка принадлежности всё равно идёт по нижнему регистру.
|
||||
section = task["section_raw"]
|
||||
if a.section is not None:
|
||||
if old_home == new_home:
|
||||
raise Usage("--section у edit — только вместе со сменой типа, меняющей"
|
||||
f" индекс. Секция внутри индекса — это move (он пишет причину):"
|
||||
f" tasks.py move {a.slug} --section {a.section} --reason …")
|
||||
section = a.section.strip().lower()
|
||||
section = a.section.strip()
|
||||
|
||||
# Смена типа между целью и задачей — это переезд между индексами, а не
|
||||
# отказ: задача лежит ровно в одном индексе, неоднозначности нет.
|
||||
@@ -1792,7 +1829,7 @@ def cmd_edit(lay: Layout, a: argparse.Namespace) -> int:
|
||||
raise Usage(f"смена типа переносит строку в {lay.name(new_home)},"
|
||||
f" а секции «{section}» там нет (есть: {avail}) —"
|
||||
f" задай `--section <из перечисленных>`")
|
||||
section = section_name.lower()
|
||||
section = section_name
|
||||
|
||||
# Тип передаётся всегда, а не только при `--type`: у файла, не переехавшего
|
||||
# на поле, он выведен из прежнего дома, и без него пересборка меты назвала
|
||||
@@ -1851,6 +1888,14 @@ def cmd_edit(lay: Layout, a: argparse.Namespace) -> int:
|
||||
|
||||
|
||||
def cmd_move(lay: Layout, a: argparse.Namespace) -> int:
|
||||
"""Перестановка строки: внутри своей секции или в другую.
|
||||
|
||||
`--section` необязателен, и это не удобство. Перестановка внутри секции —
|
||||
самая частая операция груминга (`move --after` и есть расстановка
|
||||
приоритета), а требовать в ней повторить текущую секцию значит приглашать
|
||||
указать не ту: перенос в чужую секцию выглядел бы ровно так же. Без
|
||||
`--section` секция берётся из индекса — та, в которой строка уже лежит.
|
||||
"""
|
||||
for err in (bad_slug(a.slug), bad_reason(a.reason), bad_slug(a.after) if a.after else None):
|
||||
if err:
|
||||
raise Usage(err)
|
||||
@@ -1865,10 +1910,17 @@ def cmd_move(lay: Layout, a: argparse.Namespace) -> int:
|
||||
f" ({', '.join(lay.name(k) for k in places)}) — неоднозначно,"
|
||||
f" разбери сам: tasks.py check")
|
||||
kind_index, (lines, ei) = next(iter(places.items()))
|
||||
hi, section = find_section(lines, a.section)
|
||||
if hi is None:
|
||||
avail = ", ".join(n for _, n in section_headers(lines))
|
||||
raise Usage(f"нет секции «{a.section}» в {lay.name(kind_index)} (есть: {avail})")
|
||||
if a.section is None:
|
||||
section = section_at(lines, ei)
|
||||
if not section:
|
||||
raise Usage(f"строка {a.slug} в {lay.name(kind_index)} стоит до первой"
|
||||
f" секции — переставлять внутри нечего. Назови секцию:"
|
||||
f" tasks.py move {a.slug} --section <секция> --reason …")
|
||||
else:
|
||||
hi, section = find_section(lines, a.section)
|
||||
if hi is None:
|
||||
avail = ", ".join(n for _, n in section_headers(lines))
|
||||
raise Usage(f"нет секции «{a.section}» в {lay.name(kind_index)} (есть: {avail})")
|
||||
task = parse_task(path)
|
||||
new_text = meta_updated(path, section=section, reason=a.reason,
|
||||
rtype=task["type"] or None)
|
||||
@@ -1888,7 +1940,10 @@ def cmd_move(lay: Layout, a: argparse.Namespace) -> int:
|
||||
plan.file(path, new_text)
|
||||
plan.index(lay, kind_index, lines)
|
||||
plan.commit()
|
||||
print(f"{a.slug}: перенесено в «{section}» ({lay.name(kind_index)})")
|
||||
where = ("первой" if a.first else f"после {a.after}" if a.after else "в конец")
|
||||
print(f"{a.slug}: " + (f"перенесено в «{section}»" if a.section is not None
|
||||
else f"переставлено внутри «{section}»")
|
||||
+ f", {where} ({lay.name(kind_index)})")
|
||||
return EXIT_OK
|
||||
|
||||
|
||||
@@ -1946,7 +2001,7 @@ def cmd_close(lay: Layout, a: argparse.Namespace) -> int:
|
||||
reason = a.reason.rstrip()
|
||||
dot = "" if reason.endswith((".", "!", "?")) else "."
|
||||
bullet = (f"- {today} `{a.slug}` — {task['title']}. Причина: {reason}{dot}"
|
||||
f" Была секция: {task['section'] or '—'}.")
|
||||
f" Была секция: {task['section_raw'] or '—'}.")
|
||||
rej = lay.index("rejected")
|
||||
prev = rej.read_text(encoding="utf-8") if rej.exists() else "# Ушедшее без реализации\n"
|
||||
if not prev.endswith("\n"):
|
||||
@@ -2039,7 +2094,9 @@ def cmd_reopen(lay: Layout, a: argparse.Namespace) -> int:
|
||||
plan.file(path, text)
|
||||
|
||||
target = home_index({"type": rtype})
|
||||
section = tmp["section"]
|
||||
# Написание — из меты как есть: имя секции уедет в доклад, а сверка
|
||||
# принадлежности всё равно идёт по нижнему регистру (`find_section`).
|
||||
section = tmp["section_raw"] or tmp["section"]
|
||||
lines = read_lines(lay.index(target))
|
||||
# Строка достигнутого снимается ДО вставки и на том же списке: иначе вторая
|
||||
# правка читает индекс с диска, где первой ещё нет, и затирает её.
|
||||
@@ -2058,6 +2115,16 @@ def cmd_reopen(lay: Layout, a: argparse.Namespace) -> int:
|
||||
avail = ", ".join(n for _, n in section_headers(lines))
|
||||
raise Usage(f"секции «{section}» нет в {lay.name(target)} (есть: {avail})")
|
||||
insert_entry(lines, sec, entry_line(lay, title, a.slug, tmp["why"]))
|
||||
if target == "backlog":
|
||||
# Место сырья производно от типа, и `reopen` обязан его соблюсти
|
||||
# сразу: вернуть разведку без «Вопроса» просто в конец секции —
|
||||
# значит поставить её после сырья, лежавшего там раньше, и получить
|
||||
# ошибку `check` на ровном месте. Возвращаемого файла ещё нет на
|
||||
# диске, поэтому он добавляется к набору вручную.
|
||||
raw = {n for n, t in tasks_of(lay).items() if raw_research(lay, t)}
|
||||
if raw_research(lay, tmp):
|
||||
raw.add(f"{a.slug}.md")
|
||||
lines[:] = raw_last(lines, raw)
|
||||
plan.index(lay, target, lines)
|
||||
elif unachieved:
|
||||
plan.index(lay, target, lines)
|
||||
@@ -2378,8 +2445,7 @@ def apply_fixes(lay: Layout) -> tuple[list[str], list[str]]:
|
||||
if ei is None:
|
||||
raise RuntimeError(f"{name}: строка в {lay.name(kind)} пропала посреди"
|
||||
f" прохода — чинить нечего, отчёт был бы враньём")
|
||||
cur = next((m.group(1) for j in range(ei, -1, -1)
|
||||
if (m := SECTION.match(idx[kind][j]))), None)
|
||||
cur = section_at(idx[kind], ei)
|
||||
if cur and cur.lower() != section.lower():
|
||||
insert_entry(idx[kind], section, idx[kind].pop(ei))
|
||||
fixed.append(f"{lay.name(kind)}: перенесена в секцию «{section}»: {name}")
|
||||
@@ -3012,11 +3078,11 @@ def cmd_adopt_apply(a: argparse.Namespace) -> int:
|
||||
print(f" задач, не собравших разделы своего типа: {len(unfit)} —"
|
||||
f" check это ошибкой не считает, но `ready` их не пропустит:"
|
||||
f" брать сегодня физически нечего")
|
||||
print(f" закрывается порциями груминга по 5–8 задач (скилл groom):"
|
||||
f" проставить цели, превратить «готово, когда» в критерии с оракулами,"
|
||||
f" вынуть вопросы из прозы в раздел. Готовность к первой задаче —"
|
||||
f" не «check зелёный», а «есть {CRITERIA_MIN}+ критериев хотя бы у набора"
|
||||
f" под одну цель».")
|
||||
print(" закрывается порциями груминга по 5–8 задач (скилл groom):"
|
||||
" проставить цели, превратить «готово, когда» в критерии с оракулами,"
|
||||
" вынуть вопросы из прозы в раздел. Готовность к первой задаче —"
|
||||
" не «check зелёный», а «`ready` пропускает хотя бы верхние строки"
|
||||
" очереди»: берут по одной, и годной обязана быть та, которую берут.")
|
||||
print(" источники не удалены: сверь глазами и убери сам"
|
||||
f" ({', '.join(pl.get('sources', []))}) — удалять чужое молча нельзя.")
|
||||
print(" подписи ссылок машина не трогает: цель ссылки поправлена, а текст"
|
||||
@@ -3069,12 +3135,14 @@ def main() -> int:
|
||||
p.add_argument("--section", help="только вместе со сменой типа, меняющей индекс")
|
||||
p.add_argument("--dir")
|
||||
|
||||
p = sub.add_parser("move", help="перенести в другую категорию беклога или часть роадмапа")
|
||||
p = sub.add_parser("move", help="переставить строку: место в очереди или другая секция")
|
||||
p.add_argument("slug")
|
||||
p.add_argument("--section", required=True)
|
||||
p.add_argument("--section", help="другая категория беклога или часть роадмапа;"
|
||||
" без него — текущая секция записи")
|
||||
p.add_argument("--reason")
|
||||
g = p.add_mutually_exclusive_group()
|
||||
g.add_argument("--after", help="встать следом за этим слагом (упорядоченная часть роадмапа)")
|
||||
g.add_argument("--after", help="встать следом за этим слагом — расстановка"
|
||||
" приоритета: порядок строк беклога это очередь")
|
||||
g.add_argument("--first", action="store_true")
|
||||
p.add_argument("--dir")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user