задачи: цель упразднена, у проекта появилась стадия

Тип goal и индекс ROADMAP.md убраны: цель — зонтик над параллельными
направлениями, а у проекта на одного человека список работ линеен. Роадмап
при этом наполовину дублировал беклог, а «что уже умеет» отвечают спеки и
git log индекса. Секция «Готово» удалена, а не перенесена.

Вместо цели — ось «стадия проекта»: build (беклог это план стройки, порядок
строк значит зависимость, секция одна) и support (очередь правок, порядок
значит важность, секции — полки домена). Стадия объявляется ключом
[tasks] stage, меняется командой stage, без неё check отказывает: порядок
строк нечем прочитать.

Ушли теги goal:/decomposed, поле «Секция», раздел «Завершение», флаги
--goal и edit --section. Версия раскладки 2 → 3, перевод проекта расписан
записью журнала.
This commit is contained in:
av
2026-08-13 14:26:21 +03:00
parent 0627199a1a
commit 3849f084be
26 changed files with 1128 additions and 1485 deletions
+4 -3
View File
@@ -46,8 +46,9 @@
**Учёт работ.** Владеет каталогом задач. **Учёт работ.** Владеет каталогом задач.
- `task-track` — задачи и цели каталогом markdown-файлов, у каждой записи тип - `task-track` — задачи каталогом markdown-файлов: у каждой тип (`feature`,
(`goal`, `feature`, `fix`, `chore`, `research`), и тип задаёт её схему; `fix`, `chore`, `research`), задающий её схему, а у проекта — стадия (`build`
или `support`), решающая, что значит порядок строк беклога;
вычитывают их два отдельных прохода: `task-form` (форма записи) и вычитывают их два отдельных прохода: `task-form` (форма записи) и
`task-wording` (язык записей); `task-wording` (язык записей);
- `task-groom` — груминг беклога: что сейчас самое важное и что перестало быть - `task-groom` — груминг беклога: что сейчас самое важное и что перестало быть
@@ -472,7 +473,7 @@ python3 scripts/resync.py # переписать тела всех разо
## Проверка адресов документов ## Проверка адресов документов
Адрес документа принадлежит одному плагину, а называют его все: `docs/*` стоит Адрес документа принадлежит одному плагину, а называют его все: `docs/*` стоит
примерно в сорока местах конвейера, `tasks/ROADMAP.md` — в четырёх местах канона. примерно в сорока местах конвейера, `tasks/BACKLOG.md` — в нескольких местах канона.
Переименование в каноне до этих мест само не доходит. Переименование в каноне до этих мест само не доходит.
``` ```
+1 -1
View File
@@ -27,7 +27,7 @@ color: yellow
| почему решено так | `adr/`, источник — архивный `design.md` либо записка разведки | | почему решено так | `adr/`, источник — архивный `design.md` либо записка разведки |
| граница домена, «чем не является» | `passport.md` | | граница домена, «чем не является» | `passport.md` |
| инвариант и его severity | `CLAUDE.md` | | инвариант и его severity | `CLAUDE.md` |
| что приложение умеет и чего не умеет; порядок работ | `tasks/ROADMAP.md` | | что осталось сделать и в каком порядке | `tasks/BACKLOG.md` |
| измеренное число | `research/` | | измеренное число | `research/` |
| настройка с числовым значением | `database.md` | | настройка с числовым значением | `database.md` |
| периметр и модель угроз | `security.md` | | периметр и модель угроз | `security.md` |
+19 -43
View File
@@ -1,6 +1,6 @@
--- ---
name: task-form name: task-form
description: "Проверка формы записи каталога задач по существу: тип, разошедшийся с содержанием записи, форма заголовка по типу (цель — что приложение будет уметь, задача — что нужно сделать, разведка — о чём она), «зачем», пересказывающее заголовок вместо состояния и боли, раздел «Затрагивает» с замыслом вместо границ, критерий приёмки с оракулом только на словах, предписание процесса в теле, и связь задачи со строкой «Завершения» её цели. Читает файл цели, на которую ссылается задача. Отдаёт готовые формулировки на замену и ничего не правит сам. Язык текста (залог, оценки, стоп-слова, англицизмы) смотрит отдельный агент task-wording. Использовать после заведения или разбора пачки записей, при проверке готовности перед взятием в работу (tasks.py ready) и на груминге. Только чтение." description: "Проверка формы записи каталога задач по существу: тип, разошедшийся с содержанием записи, форма заголовка по типу (задача — что нужно сделать, разведка — о чём она), «зачем», пересказывающее заголовок вместо состояния и боли, раздел «Затрагивает» с замыслом вместо границ, критерий приёмки с оракулом только на словах, предписание процесса в теле. Отдаёт готовые формулировки на замену и ничего не правит сам. Язык текста (залог, оценки, стоп-слова, англицизмы) смотрит отдельный агент task-wording. Использовать после заведения или разбора пачки записей, при проверке готовности перед взятием в работу (tasks.py ready) и на груминге. Только чтение."
tools: Read, Grep, Glob tools: Read, Grep, Glob
model: sonnet model: sonnet
color: green color: green
@@ -11,8 +11,8 @@ color: green
открывая код. открывая код.
Оптика — смысл записи в её собственных рамках. Ты **не** судишь, нужна ли Оптика — смысл записи в её собственных рамках. Ты **не** судишь, нужна ли
задача, верно ли выбрана цель и не крупна ли она: это разбор, и его ведёт задача и не крупна ли она: это разбор, и его ведёт человек со скиллом
человек со скиллом `task-track`. `task-track`.
Границу с языком держи твёрдо. **Залог, оценки, стоп-слова, англицизмы, жаргон** Границу с языком держи твёрдо. **Залог, оценки, стоп-слова, англицизмы, жаргон**
у агента `task-wording`, и тебе они не поручены даже там, где бросаются в у агента `task-wording`, и тебе они не поручены даже там, где бросаются в
@@ -28,8 +28,6 @@ color: green
## Что тебе дают ## Что тебе дают
Список файлов записей (`tasks/items/<slug>.md`) или каталог задач целиком. Список файлов записей (`tasks/items/<slug>.md`) или каталог задач целиком.
Каталог тебе нужен и сам по себе: задача несёт тег `goal:<слаг>`, и **файл цели
ты открываешь**, иначе седьмое правило не проверить.
Документы проекта — паспорт, архитектура, конвенции — если зовущий их назвал. Документы проекта — паспорт, архитектура, конвенции — если зовущий их назвал.
По ним видно, названа ли граница именем, которое в проекте существует. По ним видно, названа ли граница именем, которое в проекте существует.
@@ -43,20 +41,15 @@ color: green
| Тип | Отвечает на | Форма | | Тип | Отвечает на | Форма |
| --- | --- | --- | | --- | --- | --- |
| 🎯 `goal` | что приложение будет уметь | утверждение о возможности: «Соперником может быть компьютер» |
| ✨ `feature`, 🐞 `fix`, 🧹 `chore` | что нужно сделать | глагол в неопределённой форме, допускается «не» перед ним: «Печатать поле одним куском кода» | | ✨ `feature`, 🐞 `fix`, 🧹 `chore` | что нужно сделать | глагол в неопределённой форме, допускается «не» перед ним: «Печатать поле одним куском кода» |
| 🔬 `research` | о чём разведка | назывное, без обещания: «Подсказка следующего хода» | | 🔬 `research` | о чём разведка | назывное, без обещания: «Подсказка следующего хода» |
Описательный заголовок задачи («Лишние символы молча отбрасываются») называет Описательный заголовок задачи («Лишние символы молча отбрасываются») называет
**состояние** и одинаково читается как жалоба и как задание. Заголовок цели в **состояние** и одинаково читается как жалоба и как задание.
форме действия («Сделать соперника-компьютер») превращает роадмап в список
работ — а он список возможностей.
**Область работ — не цель.** «Работа со слиянием», «Рефакторинг вывода» не **Область работ — не задача.** «Работа со слиянием», «Рефакторинг вывода» не
отвечают ни на один из трёх вопросов; предложи возможность, которую эта работа отвечают ни на один из двух вопросов; предложи формулировку, называющую, что
создаёт, и скажи, если из текста её не видно. **Свойство поведения — нужно сделать, и скажи, если из текста этого не видно.
законная возможность**: «исход слияния не зависит от порядка доставки» — цель,
а не абстракция.
2. **Тип сходится с тем, что в записи написано.** Тип — первое поле меты, и он 2. **Тип сходится с тем, что в записи написано.** Тип — первое поле меты, и он
решает, каких разделов запись требует; разошедшийся тип врёт ровно там, где решает, каких разделов запись требует; разошедшийся тип врёт ровно там, где
@@ -68,7 +61,7 @@ color: green
- **`feature`, после которой снаружи ничего не меняется**, — это `chore`, и - **`feature`, после которой снаружи ничего не меняется**, — это `chore`, и
сказать это честно дешевле, чем выдумывать пользовательскую пользу; сказать это честно дешевле, чем выдумывать пользовательскую пользу;
- **`chore`, меняющий наблюдаемое поведение**, — это `feature` или `fix`, и у - **`chore`, меняющий наблюдаемое поведение**, — это `feature` или `fix`, и у
них другие требования (цель, воспроизведение); последнего другие требования (воспроизведение);
- **`research`, у которого «Вопрос» — это тема, а не вопрос.** «Разобраться с - **`research`, у которого «Вопрос» — это тема, а не вопрос.** «Разобраться с
выводом в терминалах» вопросом не является: на него нельзя ответить. Пока выводом в терминалах» вопросом не является: на него нельзя ответить. Пока
вопроса нет, запись остаётся сырьём — и это законное состояние, но назови вопроса нет, запись остаётся сырьём — и это законное состояние, но назови
@@ -105,20 +98,6 @@ color: green
постановке. Он же путь понизить требования решением, принятым до постановке. Он же путь понизить требования решением, принятым до
проектирования. проектирования.
7. **Задача называет, какую строку «Завершения» своей цели она двигает.**
Открой файл цели из тега `goal:<слаг>` и сверь. Три исхода, и все три —
разные находки:
- **строка не названа** — допиши предложение, какая это строка, если из текста
задачи видно; не видно — так и скажи;
- **строки с таким смыслом в «Завершении» нет** — либо задача не про эту цель,
либо у цели неполное «Завершение». Назови оба варианта, выбирать не тебе;
- **строка «Завершения», к которой не относится ни одна поданная задача**, —
это незакрытая часть возможности. Скажи о ней отдельно, вне списка находок
по файлам: это про набор, а не про запись.
У задачи **без цели** (`fix`, `chore`, `research`) правило не применяется
вовсе — они служат работоспособности, а не направлению.
## Чего ты не проверяешь ## Чего ты не проверяешь
@@ -126,13 +105,13 @@ color: green
**Чужому подрядчику — строкой в границах покрытия.** Язык у `task-wording`; **Чужому подрядчику — строкой в границах покрытия.** Язык у `task-wording`;
согласованность документов канона между собой у `doc-consistency`, их согласованность документов канона между собой у `doc-consistency`, их
соответствие коду у `doc-code-drift` — до задач эти двое не доходят вовсе, но соответствие коду у `doc-code-drift` — до задач эти двое не доходят вовсе.
если ты открыл цель и увидел расхождение в самом документе, оно их. Увидел — Увидел не своё — назови в конце одной строкой, чтобы находка не пропала, но
назови в конце одной строкой, чтобы находка не пропала, но находкой не оформляй. находкой не оформляй.
**Машинной проверке — вообще ничего.** Всё, что ловит `tasks.py check` (состав и **Машинной проверке — вообще ничего.** Всё, что ловит `tasks.py check` (состав и
написание секций, теги, тег `question` при непустом разделе «Вопросы», написание секций, теги, тег `question` при непустом разделе «Вопросы»,
согласованность индексов, битые ссылки, форма заголовка как строки), **не пиши согласованность индекса, битые ссылки, форма заголовка как строки), **не пиши
даже строкой**: это не потерянная находка, а уже проверенное. Повторять машинную даже строкой**: это не потерянная находка, а уже проверенное. Повторять машинную
проверку словами — заводить второй дом для одного правила. проверку словами — заводить второй дом для одного правила.
@@ -142,9 +121,9 @@ color: green
твоя — раздел, который **есть и лжёт**: границы вместо замысла, критерий с твоя — раздел, который **есть и лжёт**: границы вместо замысла, критерий с
оракулом только на словах. оракулом только на словах.
**Содержание работы**: нужна ли задача, верно ли выбрана цель, не крупна ли она, **Содержание работы**: нужна ли задача, не крупна ли она, достаточна ли
достаточна ли декомпозиция. Седьмое правило подходит к этому близко и декомпозиция. **И место в списке**: порядок строк значит зависимость на стройке и
останавливается там, где кончается сверка с текстом цели. Об этом молчи. важность на доработке, а ты записи видишь поштучно, вне списка. Об этом молчи.
## Порог вмешательства ## Порог вмешательства
@@ -169,9 +148,9 @@ color: green
## Доклад ## Доклад
Находки по одной, в порядке важности: заголовок → «зачем» → границы → критерии Находки по одной, в порядке важности: заголовок → «зачем» → границы → критерии.
связь с целью. Порядок такой, потому что заголовок и «зачем» — это всё, что Порядок такой, потому что заголовок и «зачем» — это всё, что видно в индексе, а
видно в индексе, а по индексу и выбирают. по индексу и выбирают.
``` ```
<файл> <файл>
@@ -181,11 +160,8 @@ color: green
почему: <одна фраза> почему: <одна фраза>
``` ```
Отдельным блоком после находок — **строки «Завершения» без задач**, если такие
нашлись: цель, строка, и что это значит.
В конце — **границы покрытия**: сколько записей просмотрено из скольких, какие В конце — **границы покрытия**: сколько записей просмотрено из скольких, какие
цели открыты, какие не смотрел и почему. Отчёт без этой строки читается как не смотрел и почему. Отчёт без этой строки читается как
«беклог проверен», не сообщая, какая его часть осталась нетронутой. Туда же — «беклог проверен», не сообщая, какая его часть осталась нетронутой. Туда же —
строка «замечено не по моей части», если бросился в глаза язык; машинно строка «замечено не по моей части», если бросился в глаза язык; машинно
проверяемое в неё **не идёт**. проверяемое в неё **не идёт**.
+10 -11
View File
@@ -1,18 +1,18 @@
--- ---
name: task-wording name: task-wording
description: "Вычитка языка записей каталога задач по информационному стилю — задачи, цели, строки индексов и причины отказа. Смотрит отглагольные существительные и страдательный залог, оценку без факта, стоп-слова и канцелярит, «одна мысль — одно предложение», англицизм при живом русском слове, жаргон и метафоры вместо прямого называния, термин, которого нет в документах проекта, транслит в слаге. Отдаёт готовые формулировки на замену и ничего не правит сам. Форму записи (заголовок по типу, «зачем», границы, оракулы) смотрит отдельный агент task-form, документы проекта вычитывает doc-wording. Использовать после заведения или разбора пачки записей, до взятия в работу и на переоценке беклога. Только чтение." description: "Вычитка языка записей каталога задач по информационному стилю — задачи, строки индекса и причины отказа. Смотрит отглагольные существительные и страдательный залог, оценку без факта, стоп-слова и канцелярит, «одна мысль — одно предложение», англицизм при живом русском слове, жаргон и метафоры вместо прямого называния, термин, которого нет в документах проекта, транслит в слаге. Отдаёт готовые формулировки на замену и ничего не правит сам. Форму записи (заголовок по типу, «зачем», границы, оракулы) смотрит отдельный агент task-form, документы проекта вычитывает doc-wording. Использовать после заведения или разбора пачки записей, до взятия в работу и на переоценке беклога. Только чтение."
tools: Read, Grep, Glob tools: Read, Grep, Glob
model: sonnet model: sonnet
color: green color: green
--- ---
Ты — **вычитка языка записей каталога задач**: задач, целей, строк индексов и Ты — **вычитка языка записей каталога задач**: задач, строк индекса и причин
причин отказа. Оптика — слова и фразы, а не то, что запись описывает: ты не отказа. Оптика — слова и фразы, а не то, что запись описывает: ты не судишь,
судишь, нужна ли задача, верно ли выбрана цель и правильно ли запись оформлена. нужна ли задача и правильно ли она оформлена.
Границу держи твёрдо, и она у тебя одна. **Форму записи** — тип, заголовок по Границу держи твёрдо, и она у тебя одна. **Форму записи** — тип, заголовок по
типу, «зачем» вместо пересказа, раздел «Затрагивает», годность оракулов, связь типу, «зачем» вместо пересказа, раздел «Затрагивает», годность оракулов
со строкой «Завершения» цели — смотрит `task-form`, и тебе она не поручена даже смотрит `task-form`, и тебе она не поручена даже
там, где бросается в глаза. Отсюда же исключение, и оно **против** тебя: там, где бросается в глаза. Отсюда же исключение, и оно **против** тебя:
неудачное слово **в заголовке** судит `task-form`, потому что заголовок целиком неудачное слово **в заголовке** судит `task-form`, потому что заголовок целиком
его. Увидел не по своей части — скажи одной строкой в конце доклада, не его. Увидел не по своей части — скажи одной строкой в конце доклада, не
@@ -27,9 +27,8 @@ color: green
## Что тебе дают ## Что тебе дают
Список записей или каталог задач: файлы `items/<slug>.md`, а с ними — индексы Список записей или каталог задач: файлы `items/<slug>.md`, а с ними —
(`BACKLOG.md`, `ROADMAP.md`, `REJECTED.md`), где та же запись представлена `BACKLOG.md` и `REJECTED.md`, где та же запись представлена строкой. **Строка индекса вычитывается наравне с файлом**: по ней запись
строкой. **Строка индекса вычитывается наравне с файлом**: по ней запись
выбирают, не открывая тела, и «зачем» в ней повторяется дословно. выбирают, не открывая тела, и «зачем» в ней повторяется дословно.
Плюс, если зовущий их назвал, документы проекта — паспорт, архитектура, Плюс, если зовущий их назвал, документы проекта — паспорт, архитектура,
@@ -200,8 +199,8 @@ color: green
критериев `check` только считает — поимённо их судит `tasks.py ready`, и это критериев `check` только считает — поимённо их судит `tasks.py ready`, и это
тоже не твоя находка: твоя — язык того, что уже написано. тоже не твоя находка: твоя — язык того, что уже написано.
**Содержание работы**: нужна ли задача, верно ли выбрана цель, не крупна ли она, **Содержание работы**: нужна ли задача, не крупна ли она, достаточна ли
достаточна ли декомпозиция. Это разбор, и его ведёт человек со скиллом `task-track`. декомпозиция. Это разбор, и его ведёт человек со скиллом `task-track`.
**Полезное действие, параллельность и работающий заголовок** — тоже не твои. **Полезное действие, параллельность и работающий заголовок** — тоже не твои.
Они в доктрине языка, судит их человек: находка по ним требует увидеть текст Они в доктрине языка, судит их человек: находка по ним требует увидеть текст
+11 -2
View File
@@ -16,7 +16,8 @@
| Ось | Значения | Дом | | Ось | Значения | Дом |
| --- | --- | --- | | --- | --- | --- |
| тип записи | `goal` `feature` `fix` `chore` `research` | `task-track/SKILL.md`, «Тип записи» | | стадия проекта | `build` `support` | `task-track/SKILL.md`, «Две стадии» |
| тип записи | `feature` `fix` `chore` `research` | `task-track/SKILL.md`, «Тип записи» |
| сценарий | решение · обслуживание · разведка | `code-resolve/SKILL.md`, «Развилка» | | сценарий | решение · обслуживание · разведка | `code-resolve/SKILL.md`, «Развилка» |
| метка | `small` `medium` `large` | `code-review/SKILL.md`, «Метки» | | метка | `small` `medium` `large` | `code-review/SKILL.md`, «Метки» |
| режим прогона | с меткой · без метки | здесь, ниже | | режим прогона | с меткой · без метки | здесь, ниже |
@@ -35,6 +36,9 @@
| Влияет | На что | Где описано | | Влияет | На что | Где описано |
| --- | --- | --- | | --- | --- | --- |
| стадия проекта | что значит порядок строк беклога: зависимость или важность | `task-track/SKILL.md`, «Две стадии» |
| стадия проекта | сколько у беклога секций, как его пополняют, применим ли груминг | там же и `task-groom/SKILL.md`, «Груминг — операция доработки» |
| стадия проекта | метку, глубину и тип — **не влияет, и это записано явно** | `task-track/SKILL.md`, «Тип записи» |
| тип записи | сценарий — **предлагает**, подтверждает предмет работы | `task-track/SKILL.md`, «Тип записи» | | тип записи | сценарий — **предлагает**, подтверждает предмет работы | `task-track/SKILL.md`, «Тип записи» |
| тип записи | метку и глубину — **не влияет, и это записано явно** | там же | | тип записи | метку и глубину — **не влияет, и это записано явно** | там же |
| сценарий | режим прогона: обслуживание идёт без метки | `code-resolve/references/maintain.md` | | сценарий | режим прогона: обслуживание идёт без метки | `code-resolve/references/maintain.md` |
@@ -44,7 +48,7 @@
| категория документа | заводит ли документ направление проверки | `canon.md`, «Три категории» | | категория документа | заводит ли документ направление проверки | `canon.md`, «Три категории» |
| severity | что с находкой делают дальше | `code-review/SKILL.md`, «Что происходит с находками» | | severity | что с находкой делают дальше | `code-review/SKILL.md`, «Что происходит с находками» |
**Две клетки пусты, и это сказано намеренно, а не забыто.** **Три клетки пусты, и это сказано намеренно, а не забыто.**
**Категория документа × режим прогона.** На прогоне **с меткой** своя тема **Категория документа × режим прогона.** На прогоне **с меткой** своя тема
проекта закрыта при любом значении: `review-basics` — приёмник проектных тем и проекта закрыта при любом значении: `review-basics` — приёмник проектных тем и
@@ -53,6 +57,11 @@
проекта в нём нет. Значит, документ, заведённый проектом как тема, на проекта в нём нет. Значит, документ, заведённый проектом как тема, на
обслуживании не смотрит никто, и строкой это нигде не называется. обслуживании не смотрит никто, и строкой это нигде не называется.
**Стадия проекта × метка.** Изменение на стройке ничем не проще того же
изменения на доработке: метку назначает разметка по факту изменения, и стадия в
неё не входит. Заманчивая мысль «на стройке всё `small`, потому что приложения
ещё нет» разбивается о первый же шаг, кладущий схему хранилища.
**Режим прогона × severity.** Триаж обязателен всегда, в том числе без метки. Но **Режим прогона × severity.** Триаж обязателен всегда, в том числе без метки. Но
часть оснований `critical` — построенный путь к отказу, замер — добывается часть оснований `critical` — построенный путь к отказу, замер — добывается
проходами, которые без метки не запускаются. Значит ли это, что `critical` на проходами, которые без метки не запускаются. Значит ли это, что `critical` на
+35 -3
View File
@@ -26,9 +26,9 @@
[tasks] [tasks]
dir = "tasks" # каталог задач от корня репозитория dir = "tasks" # каталог задач от корня репозитория
stage = "build" # стадия проекта: build | support
items = "items" # имена частей каталога — необязательны items = "items" # имена частей каталога — необязательны
backlog = "BACKLOG.md" backlog = "BACKLOG.md"
roadmap = "ROADMAP.md"
Коды выхода зовущих скриптов — общий словарь av-dev; отсюда возвращается Коды выхода зовущих скриптов — общий словарь av-dev; отсюда возвращается
исключение `ConfigError`, а решает по нему вызывающий. исключение `ConfigError`, а решает по нему вызывающий.
@@ -56,7 +56,7 @@ LEGACY_TASKS = ".tasks.json"
# Версия раскладки — одна на плагин. Журнал версий — references/changelog.md # Версия раскладки — одна на плагин. Журнал версий — references/changelog.md
# скилла `canon`, повышает его операция `upgrade`. # скилла `canon`, повышает его операция `upgrade`.
VERSION = 2 VERSION = 3
VERSION_KEY = "version" VERSION_KEY = "version"
@@ -280,6 +280,33 @@ def merge_section(root: Path, name: str, values: dict) -> list[str]:
return added return added
def set_section_key(root: Path, name: str, key: str, value: str) -> None:
"""Заменить значение ключа секции, не тронув остального.
Отличается от `merge_section` ровно тем, ради чего и заведена: та **не
трогает** ключ, который уже есть, потому что дописывает умолчания в чужой
файл. Здесь же значение меняет команда, которую позвал человек, и не
переписать его значило бы промолчать о выполненном действии. Ключа нет —
он дописывается, секции нет — заводится: и то и другое законное состояние
файла, который правят руками.
"""
path = root / CONFIG_NAME
lines = path.read_text(encoding="utf-8").splitlines() if path.is_file() else []
start = next((i for i, ln in enumerate(lines) if _is_header(ln, name)), None)
if start is None:
merge_section(root, name, {key: value})
return
end = next((i for i in range(start + 1, len(lines)) if _is_header(lines[i])),
len(lines))
pattern = re.compile(rf"^(\s*{re.escape(key)}\s*=\s*)([^#]*?)(\s*(?:#.*)?)$")
for i in range(start + 1, end):
if (match := pattern.match(lines[i])):
lines[i] = f"{match.group(1)}{quote(value)}{match.group(3)}"
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
return
merge_section(root, name, {key: value})
def missing_keys(root: Path, name: str, values: dict) -> dict: def missing_keys(root: Path, name: str, values: dict) -> dict:
"""Ключи секции, которые уже есть и разошлись с тем, что мы собирались дать. """Ключи секции, которые уже есть и разошлись с тем, что мы собирались дать.
@@ -317,7 +344,12 @@ def skeleton(number: int, docs: dict | None = None, tasks: dict | None = None) -
out += ["", "[tasks]", out += ["", "[tasks]",
"# каталог задач от корня репозитория; имена частей — умолчания скрипта", "# каталог задач от корня репозитория; имена частей — умолчания скрипта",
f"dir = {quote(tasks.get('dir', 'tasks'))}"] f"dir = {quote(tasks.get('dir', 'tasks'))}"]
for key in ("items", "backlog", "roadmap"): if tasks.get("stage"):
out += ["# стадия проекта: build — беклог это план стройки, порядок строк"
" значит зависимость;",
"# support — беклог это очередь правок, порядок значит важность",
f"stage = {quote(tasks['stage'])}"]
for key in ("items", "backlog", "rejected"):
if tasks.get(key): if tasks.get(key):
out.append(f"{key} = {quote(tasks[key])}") out.append(f"{key} = {quote(tasks[key])}")
return "\n".join(out) + "\n" return "\n".join(out) + "\n"
+8 -6
View File
@@ -1,7 +1,7 @@
# Сопровождение и эксплуатация # Сопровождение и эксплуатация
**Это дом.** Словарь «чем держат проект» назван в трёх местах трёх разных **Это дом.** Словарь «чем держат проект» назван в трёх местах трёх разных
скиллов: секция `Сопровождение` в роадмапе (`task-track`), раздел «Эксплуатация» скиллов: задачи типа `chore` (`task-track`), раздел «Эксплуатация»
в `architecture.md` (`canon`) и тема ревью `operations` (`code-review`). Ни в `architecture.md` (`canon`) и тема ревью `operations` (`code-review`). Ни
один из трёх им не владеет, поэтому дом стоит в `shared/`. один из трёх им не владеет, поэтому дом стоит в `shared/`.
@@ -18,16 +18,18 @@
| Место | Уровень | Что там | | Место | Уровень | Что там |
| --- | --- | --- | | --- | --- | --- |
| `ROADMAP.md`, секция `Сопровождение` | план | **работы**, которые собираемся делать: цели и их задачи | | `BACKLOG.md`, задачи `chore` | план | **работы**, которые собираемся делать |
| `architecture.md`, раздел «Эксплуатация» | состояние | **как устроено сейчас**: где работает, что рядом, кто перезапускает | | `architecture.md`, раздел «Эксплуатация» | состояние | **как устроено сейчас**: где работает, что рядом, кто перезапускает |
| тема ревью `operations` | оптика | **чем проверяем**: «это упало через неделю на проде» | | тема ревью `operations` | оптика | **чем проверяем**: «это упало через неделю на проде» |
Слово **«поддержка» не употребляется вовсе** — в нём слышится помощь Слово **«поддержка» не употребляется вовсе** — в нём слышится помощь
пользователю, а это другая работа. пользователю, а это другая работа. По той же причине им не названа и **стадия
проекта**: у неё имя «доработка» (`support`), словарь — `task-track`, «Две
стадии».
**Граница с возможностями проходит по тому, кто наблюдает.** «Приложение **Граница с возможностями проходит по тому, кто наблюдает.** «Приложение
сообщает о своём состоянии» — возможность приложения, её место среди прочих сообщает о своём состоянии» — возможность приложения, её место среди прочих
целей: наблюдает пользователь сервиса. «Дежурный видит состояние на одном задач: наблюдает пользователь сервиса. «Дежурный видит состояние на одном
экране» — сопровождение: наблюдаем мы. Одни и те же метрики попадают в разные экране» — сопровождение: наблюдаем мы. Одни и те же метрики попадают в задачи
секции роадмапа, и это верно — секции отвечают на разные вопросы. разных типов, и это верно — типы отвечают на разные вопросы.
+21 -22
View File
@@ -29,7 +29,7 @@
## Сопровождение и эксплуатация — целое и часть ## Сопровождение и эксплуатация — целое и часть
Словарь этой темы — [shared/operations.md](../../../shared/operations.md): Словарь этой темы — [shared/operations.md](../../../shared/operations.md):
целое и часть, три места одной темы (секция роадмапа, раздел архитектуры, тема целое и часть, три места одной темы (задачи `chore`, раздел архитектуры, тема
ревью `operations`) и граница с возможностями проекта. Здесь он не ревью `operations`) и граница с возможностями проекта. Здесь он не
пересказывается: копия жила рядом с домом в одном дереве и была ровно тем пересказывается: копия жила рядом с домом в одном дереве и была ровно тем
вторым домом, против которого правило и написано. вторым домом, против которого правило и написано.
@@ -357,36 +357,35 @@ kebab-case.** Причина не эстетическая: имя файла с
вовсе, и отказом это быть не может. вовсе, и отказом это быть не может.
Раскладку, форму записи и команды держит скилл `av-dev:task-track`. Ниже — то, Раскладку, форму записи и команды держит скилл `av-dev:task-track`. Ниже — то,
от чего зависит, читается ли проект как продукт: канон высказывается об этом от чего зависит, читается ли проект как продукт.
потому, что роадмап отвечает на вопрос о **системе**, а не о работах.
**`ROADMAP.md` отвечает на «что приложение уже умеет и чего ещё не умеет».** Это **Индекс работ один — `BACKLOG.md`, и «что приложение уже умеет» он не
не очередь работ: цель — **возможность приложения**, задача — шаг к ней. отвечает.** На этот вопрос отвечают `openspec/specs/` (нормативное поведение) и
Достигнутая цель из роадмапа **не исчезает** — строка с датой переезжает в `git log` индекса (когда и в каком порядке оно появилось). Роадмапа в каноне
секцию достигнутого, потому что «что умеет» и есть половина вопроса, ради нет: половину своего вопроса он дублировал беклогом, а вторую — спеками.
которого документ открывают. Вторым домом поведения роадмап при этом не
становится: нормативное поведение живёт в `openspec/specs/`, роадмап отвечает,
**когда и в каком порядке** оно появилось.
**У каждой записи есть тип, и тип решает, что с ней можно делать.** Дом типа — **У проекта есть стадия, и она решает, что значит порядок строк беклога:**
`build` — зависимость, `support` — важность. Канон её называет, потому что от
неё зависит, читается ли список работ как план стройки или как очередь правок;
механика — `task-track`, «Две стадии».
**У каждой задачи есть тип, и тип решает, что с ней можно делать.** Дом типа —
поле меты `Тип` первой строкой; эмодзи в заголовке от него производна. Словарь поле меты `Тип` первой строкой; эмодзи в заголовке от него производна. Словарь
закрыт: закрыт:
| Тип | Что это | | Тип | Что это |
| --- | --- | | --- | --- |
| 🎯 `goal` | возможность приложения |
| ✨ `feature` | снаружи появляется то, чего не было | | ✨ `feature` | снаружи появляется то, чего не было |
| 🐞 `fix` | поведение расходится с заявленным | | 🐞 `fix` | поведение расходится с заявленным |
| 🧹 `chore` | обслуживание, поведение не меняется | | 🧹 `chore` | обслуживание, поведение не меняется |
| 🔬 `research` | исход — знание, а не изменение | | 🔬 `research` | исход — знание, а не изменение |
**Схемы записи здесь нет намеренно.** Какие разделы тип требует, нужна ли ему **Схемы записи здесь нет намеренно.** Какие разделы тип требует — скилл
цель и берётся ли он в работу — скилл `av-dev:task-track`, раздел «Тип `av-dev:task-track`, раздел «Тип записи», подробно — по файлу на тип в его
записи», подробно — по файлу на тип в его `references/task-<тип>.md`. Канон `references/task-<тип>.md`. Канон фиксирует **словарь**, потому что от него
фиксирует **словарь**, потому что зависит, читается ли проект как продукт; схема — механика ведения задач, и второй
от него зависит, читается ли проект как продукт; схема — механика ведения задач, её экземпляр разошёлся бы с первым (он и разошёлся: канон успел объявить цель у
и второй её экземпляр разошёлся бы с первым (он и разошёлся: канон успел `fix` запрещённой, хотя она была необязательна, — и целей теперь нет вовсе).
объявить цель у `fix` запрещённой, хотя она там необязательна).
Схема требуется **к взятию в работу**, а не к заведению: беклог пополняется чаще, Схема требуется **к взятию в работу**, а не к заведению: беклог пополняется чаще,
чем разбирается, и требование на входе выгоняло бы в заметки то, что должно чем разбирается, и требование на входе выгоняло бы в заметки то, что должно
@@ -454,7 +453,7 @@ kebab-case.** Причина не эстетическая: имя файла с
| почему решено так | `adr/`, источник — архивный `design.md` либо записка разведки | | почему решено так | `adr/`, источник — архивный `design.md` либо записка разведки |
| граница домена, «чем не является» | `passport.md` | | граница домена, «чем не является» | `passport.md` |
| инвариант и его severity | `CLAUDE.md` | | инвариант и его severity | `CLAUDE.md` |
| что приложение умеет и чего не умеет; порядок работ | `tasks/ROADMAP.md` | | что осталось сделать и в каком порядке | `tasks/BACKLOG.md` |
| измеренное число | `research/` | | измеренное число | `research/` |
| настройка с числовым значением | `database.md` | | настройка с числовым значением | `database.md` |
| периметр и модель угроз | `security.md` | | периметр и модель угроз | `security.md` |
@@ -488,8 +487,8 @@ kebab-case.** Причина не эстетическая: имя файла с
| --- | --- | | --- | --- |
| `docs/review-brief.md` | документы канона и есть бриф; остаток — в `review.md` | | `docs/review-brief.md` | документы канона и есть бриф; остаток — в `review.md` |
| `docs/specs/` | `openspec/specs/` (поведение) и `architecture.md` (обзор) | | `docs/specs/` | `openspec/specs/` (поведение) и `architecture.md` (обзор) |
| `docs/drafts/` | идея → запись `research`; отказ → ADR; порядок → `ROADMAP.md`; размышление → `opsx:explore` | | `docs/drafts/` | идея → запись `research`; отказ → ADR; порядок → `BACKLOG.md`; размышление → `opsx:explore` |
| `docs/plan.md` | `tasks/ROADMAP.md` | | `docs/plan.md` | `tasks/BACKLOG.md` |
| `BRIEF.md` | `passport.md` | | `BRIEF.md` | `passport.md` |
| `docs/backlog/` | `tasks/` в корне репозитория | | `docs/backlog/` | `tasks/` в корне репозитория |
| `docs/review-journal.md`, `docs/review/journal.md` | `docs/review.md` | | `docs/review-journal.md`, `docs/review/journal.md` | `docs/review.md` |
@@ -22,6 +22,48 @@
--- ---
## Версия 3 — 2026-08-13
Тип записи `goal` и индекс `ROADMAP.md` упразднены; у проекта появилась
**стадия**`build` (беклог это план стройки, порядок строк значит зависимость)
или `support` (очередь правок, порядок значит важность).
Цель была зонтиком над параллельными направлениями — она нужна там, где список
работ нельзя выстроить в один порядок. У проекта, который ведёт один человек,
такого не бывает, и роадмап при этом наполовину дублировал беклог («чего ещё не
умеет» = «что осталось в списке»), а вторую половину («что уже умеет») отвечают
`openspec/specs/` и `git log` индекса.
**Что переехало.** Индекс остался один — `BACKLOG.md`. Поле меты `Секция` стало
`Категория`; теги `goal:<слаг>`, `decomposed` и раздел `Завершение` упразднены;
команды `list --goal`, `edit --goal`, `edit --section` и ключи `[tasks] roadmap`,
`[tasks] completion_heading` — тоже. Появились ключ `[tasks] stage`, команда
`tasks.py stage` и флаги `init --stage`, `adopt scan --stage`.
**Что сделать проекту.**
1. **Разобрать цели.** У каждой записи типа `goal` в `tasks/items/` два исхода, и
выбирает человек: она становится обычной задачей (`edit <слаг> --type
feature|fix|chore|research`) либо уходит (`close <слаг> --reason …`). Задачи,
носившие её тег, живут дальше сами по себе. Скрипт этого не решает и говорит
`НЕОДНОЗНАЧНО`.
2. **Перенести содержимое `ROADMAP.md`.** Секция `Готово` **удаляется**: «что
приложение умеет» отвечают спеки, «когда это появилось» — `git log`. Строки
`Запланировано`, `Направления` и `Сопровождение` — это цели, и они разбираются
шагом 1. Затем удалить сам файл и ключ `roadmap` из `.av-dev.toml`, если он там
был.
3. **Объявить стадию**`tasks.py stage build` или `tasks.py stage support`.
Приложение ещё строится и список работ линеен по зависимости — `build`;
работает и правится точечно — `support`. Без ключа `check` отказывает: порядок
строк нечем прочитать. На `build` секция беклога обязана остаться **одна**
слить полки надо руками, порядок строк в слитом списке знает только человек.
4. **Поднять версию**`docs.py bump`. Последним шагом.
5. `docs.py check` и `tasks.py check --dir <каталог задач>` — до отсутствия
дрейфа. Теги `goal:` и `decomposed`, поле `Секция` и старую форму меты снимет
`tasks.py check --fix`.
---
## Версия 2 — 2026-08-13 ## Версия 2 — 2026-08-13
Скилл `doc-canon` стал `canon`: префикс называл материал (`doc-`), а скилл Скилл `doc-canon` стал `canon`: префикс называл материал (`doc-`), а скилл
+2 -2
View File
@@ -32,7 +32,7 @@
# Паспорт проекта # Паспорт проекта
Зачем это и для кого. [architecture.md](architecture.md) отвечает «как Зачем это и для кого. [architecture.md](architecture.md) отвечает «как
устроено», [tasks/ROADMAP.md](../tasks/ROADMAP.md) — «в каком порядке», паспорт — устроено», [tasks/BACKLOG.md](../tasks/BACKLOG.md) — «что осталось», паспорт —
«зачем и для кого». «зачем и для кого».
## Цель ## Цель
@@ -261,7 +261,7 @@
## Последствия ## Последствия
- `+` что стало лучше. - `+` что стало лучше.
- `` чем платим: ограничения, риски, нагрузка на поддержку. - `` чем платим: ограничения, риски, нагрузка на сопровождение.
``` ```
## `docs/review.md` ## `docs/review.md`
+2 -2
View File
@@ -127,10 +127,10 @@ NOT_DOCS = {".docs.json", ".pm.json", "tasks"}
RETIRED = { RETIRED = {
"review-brief.md": "документы канона и есть бриф; остаток — в review", "review-brief.md": "документы канона и есть бриф; остаток — в review",
"review-journal.md": "→ документ review", "review-journal.md": "→ документ review",
"plan.md": "→ tasks/ROADMAP.md (ведёт скилл task-track)", "plan.md": "→ tasks/BACKLOG.md (ведёт скилл task-track)",
"local-research.md": "→ документ research", "local-research.md": "→ документ research",
"specs": "поведение → openspec/specs/, обзор → тема architecture", "specs": "поведение → openspec/specs/, обзор → тема architecture",
"drafts": "идея → запись research, отказ → ADR, порядок → ROADMAP.md", "drafts": "идея → запись research, отказ → ADR, порядок → BACKLOG.md",
"backlog": "→ tasks/ в корне репозитория (ведёт скилл task-track)", "backlog": "→ tasks/ в корне репозитория (ведёт скилл task-track)",
} }
+13 -11
View File
@@ -1,6 +1,6 @@
--- ---
name: doc-init name: doc-init
description: "Завести новый проект — сессия вопросов и ответов по свободному описанию замысла, из которой рождается первичная документация по канону av-dev: паспорт, CLAUDE.md с инвариантами и командами, модель угроз с периметром и скелет остальных документов; первые цели собирает интервью, а записывает их вызовом скилла av-dev:task-track — роадмап принадлежит учёту работ. OpenSpec заводит не сам, а вызовом скилла av-dev:code-openspec — каталог принадлежит конвейеру. Использовать, когда начинают новый проект с нуля, когда есть только текст «что мне нужно и почему» и надо превратить его в рабочую документацию, когда просят провести стартовое интервью по брифу. Проект, где документация уже как-то ведётся, переводит скилл canon." description: "Завести новый проект — сессия вопросов и ответов по свободному описанию замысла, из которой рождается первичная документация по канону av-dev: паспорт, CLAUDE.md с инвариантами и командами, модель угроз с периметром и скелет остальных документов; первый план работ собирает интервью, а записывает его вызовом скилла av-dev:task-track — беклог принадлежит учёту работ. OpenSpec заводит не сам, а вызовом скилла av-dev:code-openspec — каталог принадлежит конвейеру. Использовать, когда начинают новый проект с нуля, когда есть только текст «что мне нужно и почему» и надо превратить его в рабочую документацию, когда просят провести стартовое интервью по брифу. Проект, где документация уже как-то ведётся, переводит скилл canon."
--- ---
# Заведение нового проекта # Заведение нового проекта
@@ -32,10 +32,10 @@ description: "Завести новый проект — сессия вопро
Честная строка информативна, а не «TBD»: «архитектуры пока нет: кода нет, Честная строка информативна, а не «TBD»: «архитектуры пока нет: кода нет,
заводится первой задачей». Проход читает её как факт. заводится первой задачей». Проход читает её как факт.
**`tasks/ROADMAP.md` в таблице нет намеренно.** Первые цели `init` собирает **`tasks/BACKLOG.md` в таблице нет намеренно.** Первый план работ `init`
интервью (блок 6), но записывает их не он: каталогом задач и формой целей владеет собирает интервью (блок 6), но записывает его не он: каталогом задач владеет
`av-dev:task-track`, и это шаг 7. Человек от каталога задач отказался — цели `av-dev:task-track`, и это шаг 7. Человек от каталога задач отказался — план
остаются списком в докладе, роадмапа в проекте не появляется, и это говорится остаётся списком в докладе, беклога в проекте не появляется, и это говорится
строкой. строкой.
## Порядок интервью — зависимость, а не удобство ## Порядок интервью — зависимость, а не удобство
@@ -54,9 +54,11 @@ description: "Завести новый проект — сессия вопро
проекте нельзя откатить — деплой, выкладка наружу, перезапись данных. проекте нельзя откатить — деплой, выкладка наружу, перезапись данных.
5. **Чем краснеет гейт.** Какие проверки обязательны; что красит безусловно; 5. **Чем краснеет гейт.** Какие проверки обязательны; что красит безусловно;
чего в гейте намеренно не будет и кто тогда это гоняет. чего в гейте намеренно не будет и кто тогда это гоняет.
6. **Первые цели.** Возможности приложения, а не задачи: три-пять целей в 6. **Первые шаги стройки.** Новый проект по определению начинается со стадии
`Запланировано`, каждая — ответ на «что приложение будет уметь», с `build`: приложения ещё нет. Собери **список от базы к деталям** — что нужно
обоснованием очереди прозой. сделать, чтобы приложение заработало, — и обоснуй **порядок**: он значит
зависимость, а не важность. Пять-десять шагов достаточно: план дописывается
по ходу стройки, и это законно.
### Как вести ### Как вести
@@ -135,9 +137,9 @@ description: "Завести новый проект — сессия вопро
первом же уточнении. первом же уточнении.
6. Заведи скелет остальных по [скелетам](../canon/references/skeletons.md) — 6. Заведи скелет остальных по [скелетам](../canon/references/skeletons.md) —
каждый с честной строкой. каждый с честной строкой.
7. Каталог задач и первые цели — **вызови скилл `av-dev:task-track`**: он владеет 7. Каталог задач и первые шаги — **вызови скилл `av-dev:task-track`**: он владеет
форматом целей и задач. Не разрешился — учёт задач остаётся владельцу, и это форматом задач и стадией (`init --stage build`). Не разрешился — учёт задач
тоже строка доклада. остаётся владельцу, и это тоже строка доклада.
8. `docs.py check` из скилла `canon` — до отсутствия дрейфа. Замечания о 8. `docs.py check` из скилла `canon` — до отсутствия дрейфа. Замечания о
незаполненных плейсхолдерах остаются: их закрывает не `init`, а работа. незаполненных плейсхолдерах остаются: их закрывает не `init`, а работа.
9. **Вычитай написанное — агент `doc-wording`**, по пачке заполненных документов 9. **Вычитай написанное — агент `doc-wording`**, по пачке заполненных документов
+32 -10
View File
@@ -27,7 +27,7 @@ description: "Груминг беклога — интерактивный ра
## Три правила, из которых всё следует ## Три правила, из которых всё следует
1. **Порядок назначает человек, машина его не выводит.** Ни давность, ни тип, ни 1. **Порядок назначает человек, машина его не выводит.** Ни давность, ни тип, ни
число задач под целью приоритетом не являются. Единственное место в очереди, размер секции приоритетом не являются. Единственное место в очереди,
назначенное не человеком, — конец секции у сырья, и оно из очереди изъято назначенное не человеком, — конец секции у сырья, и оно из очереди изъято
(`task-track`, правило 4). (`task-track`, правило 4).
2. **Порция важнее охвата.** Тридцать задач за заход — это усталость и 2. **Порция важнее охвата.** Тридцать задач за заход — это усталость и
@@ -37,6 +37,28 @@ description: "Груминг беклога — интерактивный ра
`--reason` у закрытия и переноса, ответ в теле задачи, строка в докладе. `--reason` у закрытия и переноса, ответ в теле задачи, строка в докладе.
Решение, оставшееся в переписке, будет принято заново через месяц. Решение, оставшееся в переписке, будет принято заново через месяц.
## Груминг — операция доработки
**Стадия проекта решает, применим ли груминг вообще** (дом стадии —
[`task-track`, «Две стадии»](../task-track/SKILL.md#две-стадии); посмотреть —
`tasks.py stage`).
На **доработке** он и есть основная гигиена: беклог пополняется извне и
вразнобой, порядок значит важность, и назначить её может только человек.
На **стройке** оба вопроса скилла отвечены заранее. «Что сейчас самое важное» —
первая строка плана, и назначил её не приоритет, а зависимость: переставить её
значит сломать стройку. «Что перестало быть важным» возникает не порциями, а
разом — когда меняется замысел, — и тогда пересматривается **план целиком**, а
не 5–8 задач из середины. Порционный разбор здесь вреден: он вынимает шаги из
списка, порядок которого и есть его содержание.
Поэтому на стройке скилл говорит это строкой и **предлагает другую работу**:
пересмотр плана целиком, гигиену полей (`task-track`) или переход в доработку,
если беклог исчерпан. Три вещи он делает и там, потому что от стадии они не
зависят: `tasks.py check --fix`, разбор накопившихся вопросов и закрытие того,
что сделано попутно.
## Когда груминг созрел ## Когда груминг созрел
**Зовёт человек.** Скилл сам себя не назначает, но обязан **напоминать**, и **Зовёт человек.** Скилл сам себя не назначает, но обязан **напоминать**, и
@@ -122,7 +144,7 @@ flowchart TD
**3. Что перестало быть важным.** Порциями по 5–8. Сперва то, что решается **3. Что перестало быть важным.** Порциями по 5–8. Сперва то, что решается
фактом и не требует ничьего суждения (сделано попутно, отменено решением, фактом и не требует ничьего суждения (сделано попутно, отменено решением,
дублируется, симптомы одного дефекта), затем то, что решает человек (жива ли, дублируется, симптомы одного дефекта), затем то, что решает человек (жива ли,
та ли цель, задача ли это ещё). задача ли это ещё).
**4. Что важно сейчас.** Расстановка порядка — `move --after <слаг>` и **4. Что важно сейчас.** Расстановка порядка — `move --after <слаг>` и
`move --first`. Разбирается **не весь беклог, а верх очереди**: первые три-пять `move --first`. Разбирается **не весь беклог, а верх очереди**: первые три-пять
@@ -146,15 +168,15 @@ flowchart TD
кодом стоит меньше, чем та же работа через квартал; кодом стоит меньше, чем та же работа через квартал;
- **что дорожает от ожидания** — данные копятся, миграция усложняется, внешний - **что дорожает от ожидания** — данные копятся, миграция усложняется, внешний
срок приближается; срок приближается;
- **цель, которую человек назвал следующей.** - **то, что человек назвал следующим.**
Довод **записывается причиной** (`move --after <слаг> --reason …`). Порядок без Довод **записывается причиной** (`move --after <слаг> --reason …`). Порядок без
причины — это порядок, который на следующем груминге назначат заново с нуля. причины — это порядок, который на следующем груминге назначат заново с нуля.
**Цель и приоритет — независимые оси.** Очередь может идти поперёк целей, и это **Тема и приоритет — независимые оси.** Очередь может идти поперёк полок, и это
законно: задачи одной цели не обязаны стоять подряд. Но если под целью годами законно: задачи одной темы не обязаны стоять подряд. Но если из одной полки
ничего не поднимается наверх — это разговор про цель, а не про очередь, и он годами ничего не поднимается наверх — это разговор про саму работу, а не про
идёт на шаге 3. очередь, и он идёт на шаге 3.
## Документы устаревают тем же ходом работы ## Документы устаревают тем же ходом работы
@@ -231,11 +253,11 @@ flowchart TD
- Что просмотрено: N из M, сколько порций, по какому признаку отобраны. - Что просмотрено: N из M, сколько порций, по какому признаку отобраны.
- Вопросы: разобрано N, из них отвечено без человека N, снято тегов N. - Вопросы: разобрано N, из них отвечено без человека N, снято тегов N.
- **Что перестало быть важным**: удалено как реализованное (со ссылками), ушло - **Что перестало быть важным**: удалено как реализованное (со ссылками), ушло
без реализации (с причинами), понижено до сырья, слито, сменило тип или цель. без реализации (с причинами), понижено до сырья, слито, сменило тип.
- **Что важно сейчас**: верх очереди по каждой секции — слаги в порядке, и по - **Что важно сейчас**: верх очереди по каждой секции — слаги в порядке, и по
каждому движению довод одной строкой. каждому движению довод одной строкой.
- **Границы покрытия**: сколько задач не трогали и какие именно секции, теги или - **Границы покрытия**: сколько задач не трогали и какие именно секции или теги
цели остались — иначе доклад читается как «беклог разобран». остались — иначе доклад читается как «беклог разобран».
- `tasks.py check` после правок — результат строкой. - `tasks.py check` после правок — результат строкой.
## Чего этот скилл не делает ## Чего этот скилл не делает
+12 -19
View File
@@ -41,8 +41,8 @@
проверки на нужность. Свежесть меряется git'ом, как и залежалость, — датой проверки на нужность. Свежесть меряется git'ом, как и залежалость, — датой
появления файла в истории; появления файла в истории;
2. дальше **по залежалости**`list --stale`; 2. дальше **по залежалости**`list --stale`;
3. по потребности — одна секция целиком, один тег (партия ревью), одна цель 3. по потребности — одна секция целиком, один тег (партия ревью), список от
(`--goal`), список от человека. человека.
- **Останавливайся на границе порции**, даже если «ещё чуть-чуть осталось». - **Останавливайся на границе порции**, даже если «ещё чуть-чуть осталось».
Между порциями — промежуточный доклад. Между порциями — промежуточный доклад.
@@ -84,20 +84,14 @@
6. **Жива ли она вообще.** Контекст мог измениться: ушла зависимость, отпал 6. **Жива ли она вообще.** Контекст мог измениться: ушла зависимость, отпал
сценарий, обошли иначе. Здесь и звучит вопрос, выкидывать ли. сценарий, обошли иначе. Здесь и звучит вопрос, выкидывать ли.
7. **Та ли цель — и нужна ли она вообще.** `feature`, которой не находится цель, 7. **Тот ли тип.** Заводилась починкой, а после разбора оказалось, что
— кандидат на выход: новая возможность вне цели это возможность, которой никто поведение никогда и не было заявлено, — это `feature`. Тип, оставшийся от
не заказывал. Операционной задаче (`fix`, `chore`, `research`) цель не нужна, прошлой формулировки, врёт ровно там, где по нему отбирают, и требует не тех
и выдумывать её здесь не надо. разделов.
**Отменяется и сама цель** — когда замысел оказался неверен, а не когда
задача выбрала не ту. Тогда порция расширяется до всех задач этой цели: каждую
либо закрыть своей причиной, либо перевесить на другую цель, и только потом
закрыть цель. Порядок и почему он такой —
[task-goal.md](../../task-track/references/task-goal.md#отменённая-цель--сперва-задачи-потом-цель).
8. **Задача ли это по-прежнему.** Не проходит `ready` по существу, а не по 8. **Задача ли это по-прежнему.** Не проходит `ready` по существу, а не по
недописанным разделам → `edit <slug> --type research` и опустошённый раздел недописанным разделам → `edit <slug> --type research` и опустошённый раздел
«Вопрос», то есть сырьё; дальше штурм. Разрослась → это несколько задач под «Вопрос», то есть сырьё; дальше штурм. Разрослась → это несколько задач,
той же целью, дальше декомпозиция. дальше декомпозиция.
9. **Не подешевела ли она.** Сделанная с прошлого раза работа меняет цену 9. **Не подешевела ли она.** Сделанная с прошлого раза работа меняет цену
**других** задач: рядом с только что тронутым кодом та же работа стоит меньше. **других** задач: рядом с только что тронутым кодом та же работа стоит меньше.
Это довод и на шаге 4 — задача, внезапно подешевевшая, поднимается в очереди Это довод и на шаге 4 — задача, внезапно подешевевшая, поднимается в очереди
@@ -111,7 +105,7 @@
нигде не хранится. нигде не хранится.
Задача из верхних строк `--stale`, которую и этот заход оставляет без изменений, Задача из верхних строк `--stale`, которую и этот заход оставляет без изменений,
**либо двигается (меняет цель, поднимается в очереди, уходит с причиной), либо **либо двигается (меняет полку, поднимается в очереди, уходит с причиной), либо
остаётся с явно записанной причиной**, почему её держим (`move <slug> --reason остаётся с явно записанной причиной**, почему её держим (`move <slug> --reason
` — без `--section` секция берётся текущая). Молчаливое «оставить как есть» на ` — без `--section` секция берётся текущая). Молчаливое «оставить как есть» на
давно неподвижной задаче — это решение не принимать решение; запись причины давно неподвижной задаче — это решение не принимать решение; запись причины
@@ -123,9 +117,8 @@
Разбирается **верх очереди**, а не весь беклог: первые три-пять строк каждой Разбирается **верх очереди**, а не весь беклог: первые три-пять строк каждой
секции. Ниже пятой строки порядок всё равно перестаёт что-либо значить. секции. Ниже пятой строки порядок всё равно перестаёт что-либо значить.
1. **Покажи текущий верх**`list --index backlog`, по секциям, в том порядке, 1. **Покажи текущий верх**`list`, по секциям, в том порядке, в каком строки
в каком строки лежат. Плюс состояние проекта из роадмапа: секция `Готово` лежат.
отвечает на «где мы», `Запланировано` — на «куда шли».
2. **Спрашивай сравнением, а не оценкой.** «Что из этих двух делают раньше» 2. **Спрашивай сравнением, а не оценкой.** «Что из этих двух делают раньше»
имеет проверяемый ответ, «насколько важна эта задача» — нет. Веди попарно и имеет проверяемый ответ, «насколько важна эта задача» — нет. Веди попарно и
сверху: что первое, что после него. сверху: что первое, что после него.
@@ -133,7 +126,7 @@
или `move <slug> --first --reason …`. Довод берётся из перечня в или `move <slug> --first --reason …`. Довод берётся из перечня в
[SKILL.md](../SKILL.md#приоритет-как-его-расставляют): сломано сейчас, [SKILL.md](../SKILL.md#приоритет-как-его-расставляют): сломано сейчас,
разблокирует остальное, дешевеет от сделанного, дорожает от ожидания, разблокирует остальное, дешевеет от сделанного, дорожает от ожидания,
названная цель. названо человеком.
4. **Проверь верх на готовность**`tasks.py ready <слаг> …` по первым строкам. 4. **Проверь верх на готовность**`tasks.py ready <слаг> …` по первым строкам.
Задача, стоящая первой и не проходящая `ready`, — это очередь, которая врёт: Задача, стоящая первой и не проходящая `ready`, — это очередь, которая врёт:
взять её нельзя. Либо дописывается здесь же, либо уступает место. взять её нельзя. Либо дописывается здесь же, либо уступает место.
+195 -244
View File
@@ -1,13 +1,13 @@
--- ---
name: task-track name: task-track
description: Ведение задач и целей как каталога markdown-файлов (одна запись = один файл в items/ + строка в одном из индексов). У каждой записи есть тип (goal, feature, fix, chore, research), и тип решает, каких разделов она требует и что с ней можно делать. Заведение записи из диалога, разбор находок аудита/ревью, декомпозиция на независимо полезные части, штурм сырья, гигиена полей и проверка согласованности индексов. Использовать, когда просят добавить задачу/идею/цель, превратить находки ревью в задачи, разбить задачу, проработать идею, поправить формат или проверить беклог. Каталог отстал от версии раскладки — это скажет tasks.py check, а повышает проект скилл av-dev:canon по общему журналу версий. Расстановка приоритетов и разбор накопившегося — скилл av-dev:task-groom. Не реализует задачи — этим занимается скилл решения задачи. description: Ведение задач как каталога markdown-файлов (одна задача = один файл в items/ + строка в BACKLOG.md). У каждой задачи есть тип (feature, fix, chore, research), и тип решает, каких разделов она требует и что с ней можно делать. У проекта есть стадия (build — беклог это план стройки, порядок строк значит зависимость; support — очередь правок, порядок значит важность). Заведение записи из диалога, разбор находок аудита/ревью, декомпозиция на независимо полезные части, штурм сырья, гигиена полей, смена стадии и проверка согласованности индекса. Использовать, когда просят добавить задачу или идею, превратить находки ревью в задачи, разбить задачу, проработать идею, поправить формат, объявить стадию или проверить беклог. Каталог отстал от версии раскладки — это скажет tasks.py check, а повышает проект скилл av-dev:canon по общему журналу версий. Расстановка приоритетов и разбор накопившегося — скилл av-dev:task-groom. Не реализует задачи — этим занимается скилл решения задачи.
--- ---
# Задачи # Задачи
Задачи — каталог markdown-файлов. Одна запись = один файл `items/<slug>.md` плюс Задачи — каталог markdown-файлов. Одна задача = один файл `items/<slug>.md` плюс
строка **ровно в одном** индексе. Скилл владеет **форматом и содержимым**: строка в `BACKLOG.md`. Скилл владеет **форматом и содержимым**: заводит,
заводит, редактирует, закрывает, разбирает находки ревью, дробит, штурмует сырьё. редактирует, закрывает, разбирает находки ревью, дробит, штурмует сырьё.
Чем он **не** владеет: **очередью** — что делать следующим и что перестало быть Чем он **не** владеет: **очередью** — что делать следующим и что перестало быть
важным, решает скилл `task-groom`, а этот скилл лишь даёт ему операции; и выполнением важным, решает скилл `task-groom`, а этот скилл лишь даёт ему операции; и выполнением
@@ -17,57 +17,53 @@ description: Ведение задач и целей как каталога mar
Ситуация не покрыта инструкцией — решай по ним. Ситуация не покрыта инструкцией — решай по ним.
0. **Цель — возможность приложения, задача — шаг к ней.** Цель отвечает на «что 0. **Стадия решает, что значит порядок строк.** Проект живёт в одной из двух
приложение будет уметь», её «Завершение» — наблюдаемый признак того, что уже стадий, и обе ведут один и тот же беклог, но читают его по-разному.
умеет; задача отвечает на «что для этого нужно сделать». Оценивается проект На **стройке** (`build`) беклог это план от базы к деталям: порядок —
по **поведению**, а не по внутреннему устройству, поэтому и роадмап отвечает зависимость, «раньше нельзя». На **доработке** (`support`) беклог это очередь
не «сколько работ осталось», а «что уже умеет и чего ещё не умеет». правок: порядок — важность, «раньше лучше». Из этого следует остальное —
Свойство поведения — тоже возможность: «сообщает о своём состоянии», сколько у беклога секций, как его пополняют, что значит его опустошение и
«исход слияния не зависит от порядка доставки» — законные цели. нужен ли груминг. Стадия объявлена ключом `[tasks] stage`; молчание ответом
1. **Беклог гниёт с той стороны, где его пополняют.** Заведение — самая частая не считается, и `check` без неё отказывает.
операция и с худшим отказом: из одного разговора рождается пять файлов, а 1. **Беклог гниёт с той стороны, где его пополняют — на доработке.** Заведение
переоценка потом разгребает то, чего не надо было заводить. Дедупликация и там самая частая операция и с худшим отказом: из одного разговора рождается
фильтр на входе дешевле любой чистки. Заводим только то, что **не делаем пять файлов, а переоценка потом разгребает то, чего не надо было заводить.
сейчас** и о потере чего пожалеем. Дедупликация и фильтр на входе дешевле любой чистки: заводим только то, что
2. **Файл — источник истины, индексы производны.** Разошлись — неправы индексы. **не делаем сейчас** и о потере чего пожалеем.
**На стройке правило не применяется**, и это не послабление. Список стройки
пишется вперёд целиком — он и есть замысел, — а фильтр «не заводи то, чего не
делаешь сейчас» запретил бы написать план дальше первого шага. Дедуп остаётся
в обеих стадиях: две записи об одном плохи всегда.
2. **Файл — источник истины, индекс производен.** Разошлись — неправ индекс.
Согласованность механизируема и проверяется командой, а не вниманием: всё, Согласованность механизируема и проверяется командой, а не вниманием: всё,
что ловит `tasks.py check`, не должно попадать ни в чек-лист, ни в промпт. что ловит `tasks.py check`, не должно попадать ни в чек-лист, ни в промпт.
Поэтому **«зачем» живёт в мете файла**, а строка индекса его лишь Поэтому **«зачем» живёт в мете файла**, а строка индекса его лишь
повторяет: пока поле лежало только в индексе, восстановление пропавшей повторяет: пока поле лежало только в индексе, восстановление пропавшей
строки теряло его молча и навсегда. Единственное исключение намеренное: **в каком строки теряло его молча и навсегда. Единственное исключение намеренное:
индексе лежит запись, знают индексы** — поля-состояния в файле нет. И **порядок строк в беклоге**он свойство списка, а не задачи, и в файле ему
**порядок строк в беклоге**: приоритет это свойство очереди, а не задачи, и в места нет (правило 4).
файле ему места нет (правило 4).
3. **Причина переживает запись.** Выкинутая без причины задача вернётся через 3. **Причина переживает запись.** Выкинутая без причины задача вернётся через
квартал тем же текстом. Реализованная оставляет след в коммите — выкинутая не квартал тем же текстом. Реализованная оставляет след в коммите — выкинутая не
оставляет ничего, поэтому у неё есть `REJECTED.md`. оставляет ничего, поэтому у неё есть `REJECTED.md`.
4. **Приоритет — это порядок строк, а цель есть не у всякой задачи.** Очередь 4. **Порядок строк — единственное, чего в файле нет.** Он значим в обеих
внутри секции беклога значима: **первая строка — то, что делают следующим**. стадиях, и назначает его человек: на стройке — раскладывая шаги по
Приоритет назначает человек на груминге, машина его не выводит и не угадывает. зависимости, на доработке — на груминге. Машина порядок не выводит и не
угадывает; всё, что она делает сама, — ставит машинную позицию в **конец**
секции и говорит об этом вслух.
Прежде здесь стояло «порядка нет, есть цель», и обосновано это было тем, что **Дом порядка — индекс, а не файл.** Положи его в файл числом, и два соседних
на «что делать дальше» отвечает **набор спринта**. Набора больше нет, а файла смогли бы утверждать одно и то же место, а строка индекса —
вопрос остался — и без порядка отвечать на него стало нечем. противоречить обоим.
**Дом приоритета — индекс, а не файл.** Это то же исключение из правила 2,
что и «в каком индексе лежит запись»: приоритет — свойство очереди. Положи он
в файл числом, и два соседних файла смогли бы утверждать одно и то же место,
а строка индекса — противоречить обоим.
Цель обязательна там, где она и есть содержание работы, — у **новой
возможности** (`feature`). Починка, техдолг и разведка служат
работоспособности, а не направлению, и живут без цели законно. Придуманная им
цель — то же враньё, от которого спасает тип. **Цель и приоритет —
независимые оси:** очередь может идти поперёк целей, и это законно.
Одно место в очереди назначено **не человеком, а типом**: **сырьё** Одно место в очереди назначено **не человеком, а типом**: **сырьё**
(`research` без раздела «Вопрос») стоит в конце своей категории. Его не берут, (`research` без раздела «Вопрос») стоит в конце своей секции. Его не берут,
и между берущимся оно каждый раз требует открыть файл, чтобы это понять. Раз и между берущимся оно каждый раз требует открыть файл, чтобы это понять. Раз
это выводится, проверяет и чинит это машина. это выводится, проверяет и чинит это машина.
5. **Тип решает, что с записью можно делать.** Тип — единственная ось и первое 5. **Тип решает, что с задачей можно делать.** Тип — вторая ось и первое
поле меты: от него зависит, какие разделы обязательны в теле, нужна ли цель, поле меты: от него зависит, какие разделы обязательны в теле и берётся ли
берётся ли запись в работу и в каком индексе живёт её строка. Словарь закрыт; запись в работу. Словарь закрыт; ни один тип не подошёл — значит, в записи их
ни один тип не подошёл — значит, в записи их два, и её надо разделить. два, и её надо разделить.
## Раскладка ## Раскладка
@@ -79,55 +75,23 @@ description: Ведение задач и целей как каталога mar
``` ```
tasks/ tasks/
items/ задачи и цели файлами, <slug>.md, слаги английские items/ задачи файлами, <slug>.md, слаги английские
ROADMAP.md состояние проекта: что уже умеет и чего ещё не умеет BACKLOG.md что можно взять. Порядок строк в секции значим,
BACKLOG.md что можно взять — только задачи, целей здесь нет. и значит он разное на разных стадиях
Порядок строк в секции значим: это очередь
REJECTED.md ушедшее БЕЗ реализации, с причиной и датой REJECTED.md ушедшее БЕЗ реализации, с причиной и датой
``` ```
Правило, снимающее путаницу: **`BACKLOG.md` — то, что берут; `ROADMAP.md` — то, **Индекс один.** `REJECTED.md` индексом не считается: он не говорит, где запись
подо что берут.** Цель в работу взять нельзя — берут её задачи, — поэтому в числится, — это кладбище ушедшего.
списке берущихся ей не место.
**Четыре секции роадмапа, и последняя отвечает на половину вопроса:** **Секции беклога называет проект**, и `check` проверяет у них ровно две вещи:
что секция есть хоть одна и что на стройке она **одна**. Смысла секции не несут
— это полки домена (`Ядро`, `Инфра`), — и подгонять их имена под свой вкус
скрипт права не имеет. Поле меты, называющее полку, зовётся **Категория**.
| Секция | Англ. | Что в ней | **Заголовок секции отбит пустой строкой с обеих сторон и написан с прописной**;
| --- | --- | --- | отбивку правит `check --fix`. Написание секции в мете файлов он тоже правит: имя
| `Запланировано` | `Planned` | очередь значима и обосновывается прозой рядом | секции принадлежит заголовку индекса, файл на неё только ссылается.
| `Направления` | `Directions` | очереди нет, тянутся долго |
| `Сопровождение` | `Operations` | чем держат проект: инструмент, процесс, эксплуатация — не возможности приложения, и потому отдельно |
| `Готово` | `Done` | достигнутые цели — строкой с датой, **без ссылки на файл**: файл удалён, поведение живёт в спеках |
**Порядок тоже канонический, и `Готово` стоит последним не из скромности.**
Достигнутое **копится**: через год этой секции больше, чем всех остальных
вместе. Стоя первой, она отодвигает за экран ровно то, ради чего роадмап
открывают чаще всего, — что делается сейчас и что дальше. Порядок проверяет
`check`, переставляет `check --fix`.
**Секции роадмапа канонические, категории беклога — нет**, и разница не в любви к
единообразию. У каждой секции роадмапа свой смысл, в достигнутое пишет сам `close`, и
роадмап, названный по-своему, читался бы только своим автором. Категории беклога
(`Ядро`, `Инфра`) смысла не несут — это полки домена, и остаются делом проекта.
Отсюда и разные имена поля меты: у цели **Секция** (часть роадмапа — состояние
очереди), у задачи **Категория** (полка домена, на которой она лежит).
Отсюда четыре правила, которые проверяет `tasks.py check`: **состав закреплён**
(чужая секция — ошибка, а не вольность), **все четыре обязаны быть** (нет
секции — нет ответа на её часть вопроса), **язык один на весь индекс**, **порядок
канонический**. `--roadmap-sections` у `init` нет: выбирать нечего.
**Заголовок секции отбит пустой строкой с обеих сторон и написан с прописной.**
Во всех индексах одинаково, включая категории беклога, которые проект называет сам.
Написание канонических секций правит `check --fix` (заодно и ссылку на секцию в
мете файлов: имя секции принадлежит заголовку индекса, файл на неё только
ссылается); отбивку и порядок он правит везде.
Оговорка про `Сопровождение`: слово `окружение` сюда не годится — в
`architecture.md` оно уже значит боевое окружение приложения, и одно слово в двух
смыслах развело бы документы канона. А `Разработка`, стоявшая тут раньше,
называла слишком много: роадмап **весь** про разработку, и секция с таким именем
не отличалась от остальных ничем.
**Секции «блокеры» в беклоге нет.** Блокер — это *состояние* (работа не может **Секции «блокеры» в беклоге нет.** Блокер — это *состояние* (работа не может
продолжаться ни одной задачей), а не полка: он живёт ровно до ответа человека, и продолжаться ни одной задачей), а не полка: он живёт ровно до ответа человека, и
@@ -138,53 +102,31 @@ tasks/
который переезжает с такой секцией, её надо удалить** — это единственное место, который переезжает с такой секцией, её надо удалить** — это единственное место,
где это сказано. где это сказано.
**Запись живёт в одном индексе за раз.** Индексов два, и выбирает между ними **Порядок строк — единственное, чего в файле нет** (правило 4). Отсюда следствие
тип: цель в роадмапе, задача в беклоге. Сменился тип — строка переезжает для всякой машинной правки индекса: восстановленная или перенесённая строка
(`edit --type`). Файл в `items/` при этом **не двигается**: он и есть запись, встаёт **в конец своей секции**, и скрипт об этом говорит. Молчаливая вставка
индексы лишь показывают, где она числится и в каком порядке стоит. выдала бы машинную позицию за решение человека — а решение это его.
**Порядок строк в беклоге — приоритет**, и он единственное, чего в файле нет
(правило 4). Отсюда следствие для всякой машинной правки индекса:
восстановленная или перенесённая строка встаёт **в конец своей секции**, и
скрипт об этом говорит. Молчаливая вставка выдала бы машинную позицию за
решение человека — а решение это его.
**У сделанной задачи записи не остаётся** — файл и строка удаляются (`close **У сделанной задачи записи не остаётся** — файл и строка удаляются (`close
--implemented`). Ей хватает коммита и документации проекта; вторая запись была --implemented`). Ей хватает коммита и документации проекта; вторая запись была
бы вторым домом для того же факта. Вопрос «что было сделано и когда» отвечается бы вторым домом для того же факта. Вопрос «что было сделано и когда» отвечается
даром: индексы лежат под git, а закрытие коммитится отдельным коммитом учёта — даром: индекс лежит под git, а закрытие коммитится отдельным коммитом учёта —
`git log -p tasks/BACKLOG.md` отдаёт историю без отдельного журнала. `git log -p tasks/BACKLOG.md` отдаёт историю без отдельного журнала.
**У достигнутой цели запись остаётся, и это единственное исключение.** Файл
удаляется так же, а строка переезжает в секцию `Готово` с датой. Причина в том,
что цель — не работа, а **возможность**: «что приложение умеет» это половина
вопроса, ради которого роадмап и открывают, и стирать её вместе с файлом значит
оставить инструмент, отвечающий только «что осталось». Вторым домом это не
становится: поведение живёт в `openspec/specs/`, а роадмап отвечает **когда и в
каком порядке оно появилось** — другой вопрос. Ссылки на файл в строке нет
намеренно: файл удалён, а битая ссылка — законная ошибка `check`.
Куда запись может переехать и какой командой — весь набор переходов: Куда запись может переехать и какой командой — весь набор переходов:
```mermaid ```mermaid
stateDiagram-v2 stateDiagram-v2
state "BACKLOG.md — что берут" as B state "BACKLOG.md — что берут" as B
state "ROADMAP.md — подо что берут" as P
state "REJECTED.md — ушла без реализации" as R state "REJECTED.md — ушла без реализации" as R
state "записи нет — реализована" as D state "записи нет — реализована" as D
state "ROADMAP.md, «умеет» — цель достигнута" as A
[*] --> B: add --type feature|fix|chore|research [*] --> B: add --type feature|fix|chore|research
[*] --> P: add --type goal B --> B: move --after | --first | --section
B --> P: edit --type goal --section
P --> B: edit --type feature|fix|chore|research --section
B --> D: close --implemented B --> D: close --implemented
P --> A: close --implemented
B --> R: close --reason B --> R: close --reason
P --> R: close --reason
D --> B: reopen --reason D --> B: reopen --reason
R --> B: reopen --reason R --> B: reopen --reason
A --> P: reopen --reason
``` ```
Состояния здесь — **где числится строка**, а не где лежит файл: файл Состояния здесь — **где числится строка**, а не где лежит файл: файл
@@ -195,87 +137,90 @@ stateDiagram-v2
Схема — **сводка**: условия и оговорки живут в тексте разделов, и при Схема — **сводка**: условия и оговорки живут в тексте разделов, и при
расхождении прав текст. расхождении прав текст.
## Цели ## Две стадии
**Цель — возможность приложения.** Такой же файл в `items/`, тип `goal` (🎯), **Стадия проекта — ось, и решает она, что значит порядок строк беклога.**
перечисленный в `ROADMAP.md`. Формулируется ответом на вопрос **«что приложение Значения два, дом — ключ `[tasks] stage` в `.av-dev.toml`.
будет уметь»**, а не названием области работ: не «Работа с чтением», а «Чтение
данных клиентами»; не «Рефакторинг слияния», а «Исход слияния не зависит от
порядка доставки».
**Свойство поведения — тоже возможность.** «Наблюдаемость» это «приложение | | `build` — стройка | `support` — доработка |
сообщает о своём состоянии»; «прочность слияния» это «исход не зависит от | --- | --- | --- |
порядка». Такие цели законны и переформулировки в функцию не требуют — требуют | Порядок строк | зависимость: раньше **нельзя** | важность: раньше **лучше** |
только, чтобы формулировка отвечала на «что приложение делает», а не на «какую | Секции | ровно одна: список от базы к деталям | полки домена, сколько нужно |
часть кода мы трогаем». | Заведение | список пишется вперёд целиком | по одной, по мере появления |
| Пустой беклог | план исчерпан, стройка окончена | нормальное состояние |
| Груминг | не применяется; замысел сменился — план пересматривается целиком | основная гигиена, порциями по 5–8 |
| Залежалость | не считается: шаг ждёт своей очереди законно | считается, `list --stale` |
**Целью не становится работа, которой держат проект.** Состав перечислен **Стадия называется явно, и молчание ответом не считается.** Без неё порядок
[в словаре сопровождения](../../shared/operations.md); строк нечем прочитать: переставить строку значит на стройке сломать план, а на
на вопрос «что приложение будет уметь» ничто из него не отвечает. Им отведена отдельная секция роадмапа, доработке — принять решение о важности, и это разные действия. `init --stage`
чтобы они были видны в том же экране и при этом не читались как возможности обязателен, `check` без ключа отказывает, `check --fix` его не подставляет:
продукта. какая стадия у проекта, знает человек, а подставленное умолчание соврало бы ровно
там, где по нему принимают решение.
**Граница проходит по тому, кто наблюдает, а не по теме.** «Приложение сообщает **Секций на стройке одна, и это не педантизм.** Порядок там — зависимость, и
о своём состоянии» — возможность: наблюдает пользователь сервиса, и цели место разложенный по полкам список перестаёт быть планом: два шага из разных секций
среди прочих. «Дежурный видит состояние на одном экране» — сопровождение: уже не сравнить. На доработке полки законны — правки независимы, и очередь
наблюдаем мы. Одна и та же наблюдаемость попадает в разные секции, и это верно — внутри полки самостоятельна.
секции отвечают на разные вопросы.
**Сопровождение и эксплуатация — целое и часть**, а не синонимы, и та же тема **Переход — событие, а не настройка.** `tasks.py stage support` переносит остаток
живёт ещё в двух местах: разделе «Эксплуатация» в `architecture.md` и теме ревью беклога в первую новую секцию, правит «Категорию» в файлах и говорит, что порядок
`operations`. Словарь у всех трёх общий, и дом у него один: с этого момента значит другое. Датой ему служит коммит: отдельного журнала ради
[shared/operations.md](../../shared/operations.md) — читается по ссылке. одной строки не заводится. Признак созревания наблюдаемый — беклог стройки
Пересказывать его своими словами нельзя: три перечня «чем держат проект» уже исчерпан, и `check` об этом напоминает; **запретить переход раньше скрипт не
разъезжались на «метриках и логах» против «мониторинга». берётся**: «приложение построено» решает человек, а не счётчик строк.
Секция выбирается так: очередь значима и обоснована прозой — `Запланировано`; Обратный переход (`stage build`) разрешён и устроен так же. Он редок — проект
тянется долго и очереди не имеет — `Направления`; не про приложение, а про то, уходит на стройку заново разве что при переделке замысла целиком, — но
чем его держат, — `Сопровождение`; в `Готово` кладёт сам `close`. запрещать его было бы запретом на то, что иногда и правда случается.
- **Список задач цели выводится, а не хранится.** В теле цели — зачем она и что ## Чего у задач больше нет
считается её завершением; перечня задач там нет. Он был бы третьим индексом и
поехал бы на первой же закрытой задаче, а `check` про него не знает. Связь **Тип `goal` и `ROADMAP.md` упразднены.** Цель была зонтиком над параллельными
однонаправленна: задача несёт тег `goal:<слаг>`, перечень даёт направлениями: она нужна там, где список работ нельзя выстроить в один порядок,
`tasks.py list --goal <слаг>`. и очередь идёт поперёк направлений. У проекта, который ведёт один человек, такого
- **Статус цели выводится.** Цель достигнута, когда у неё не осталось открытых не бывает — на стройке список линеен по зависимости, на доработке правки
задач; `[x]`/`[~]` руками не ведутся, а `close` цели с живыми задачами независимы, — и зонтик не стоял ни над чем.
скрипт запретит. Достижение — `close <цель> --implemented`: файл удаляется,
строка с датой переезжает в `Готово`. Ошиблись — `reopen` вернёт файл и Роадмап при этом отвечал на свой вопрос наполовину: «чего ещё не умеет» — это
**снимет строку достигнутого**, чтобы роадмап не утверждал того, чего нет. Единственная оговорка: цель без задач неотличима — «ещё не «что осталось в беклоге», то есть пересказ второго индекса. Вторая половина, «что
разобрана» или «всё закрыто». Различает **тег `decomposed`** в мете уже умеет», живёт в двух домах и без него: нормативное поведение — в
цели: он ставится, когда цель разложена на задачи. Тег, а не строка в теле — `openspec/specs/`, а когда и в каком порядке оно появилось — в `git log` индекса
потому что проверяется механически: `check` **напоминает** о нём у пустой цели и коммитах задач.
(замечанием, не ошибкой — неразобранная цель это законное состояние), а `check
--fix` сам проставляет его цели, у которой задачи есть. Вместе с целью ушли: секция `Готово` (её ответ дают спеки и история), теги
- **Тип `[epic]` упразднён.** Он был зонтиком между целью и задачами — «задача, `goal:<слаг>` и `decomposed`, поле меты `Секция` (осталась `Категория`), раздел
которая не мерджится целиком». Зонтик теперь цель, а слишком крупный шаг просто `Завершение` и команды `list --goal`, `edit --goal`. Встретились в проекте —
дробится на шаги помельче под той же целью, и промежуточному типу места не `check` назовёт их поимённо, `check --fix` снимет теги, а запись типа `goal`
осталось. Замер подтвердил: ноль употреблений на 97 записей двух живых оставит человеку: во что она превращается — в задачу или в ничто, — машина не
проектов. Встретился в чужом беклоге — это цель либо набор задач, и `check` решает.
назовёт его неизвестным типом.
**Тип `[epic]` упразднён раньше и не вернулся.** Слишком крупный шаг дробится на
шаги помельче, стоящие в списке подряд.
## Тип записи ## Тип записи
**Тип — единственная ось этого скилла, и он решает, что с записью можно делать.** **Тип решает, что с задачей можно делать.** Вторая ось скилла — первая стадия.
Перечень осей всего процесса и того, чего каждая **не** решает, — Перечень осей всего процесса и того, чего каждая **не** решает, —
[shared/axes.md](../../shared/axes.md). Дом типа — [shared/axes.md](../../shared/axes.md). Дом типа —
**поле меты `Тип` первой строкой**; эмодзи в заголовке H1 от него производна, её **поле меты `Тип` первой строкой**; эмодзи в заголовке H1 от него производна, её
ставит `add` и чинит `check --fix`. ставит `add` и чинит `check --fix`.
| Тип | Обязательные разделы | Цель | В работу | Устав | | Тип | Обязательные разделы | Устав |
| --- | --- | --- | --- | --- | | --- | --- | --- |
| 🎯 `goal` | `Завершение` | — | нет | [task-goal.md](references/task-goal.md) | | `feature` | `Затрагивает`, `Критерии приёмки` | [task-feature.md](references/task-feature.md) |
| `feature` | `Затрагивает`, `Критерии приёмки` | **обязательна** | да | [task-feature.md](references/task-feature.md) | | 🐞 `fix` | `Воспроизведение`, `Затрагивает`, `Критерии приёмки` | [task-fix.md](references/task-fix.md) |
| 🐞 `fix` | `Воспроизведение`, `Затрагивает`, `Критерии приёмки` | необязательна | да | [task-fix.md](references/task-fix.md) | | 🧹 `chore` | `Затрагивает`, `Критерии приёмки` | [task-chore.md](references/task-chore.md) |
| 🧹 `chore` | `Затрагивает`, `Критерии приёмки` | нет | да | [task-chore.md](references/task-chore.md) | | 🔬 `research` | `Вопрос`, `Куда ляжет ответ` | [task-research.md](references/task-research.md) |
| 🔬 `research` | `Вопрос`, `Куда ляжет ответ` | нет | да | [task-research.md](references/task-research.md) |
Берутся в работу все четыре: записи, которую нельзя взять, больше не существует.
Сверх обязательных у любой задачи допустимы `Рамки` и `Вопросы`. Раздел не из Сверх обязательных у любой задачи допустимы `Рамки` и `Вопросы`. Раздел не из
схемы своего типа — **замечание, а не ошибка**: свой раздел законная вольность схемы своего типа — **замечание, а не ошибка**: свой раздел законная вольность
проекта, но `Воспроизведение` у `chore` почти всегда значит, что тип проставлен проекта, но `Воспроизведение` у `chore` почти всегда значит, что тип проставлен
не тот, и сказать об этом стоит, не запрещая. не тот, и сказать об этом стоит, не запрещая.
**Осей было две, и ортогональность у них была фальшивой.** Тип записи **Прежних осей было две, и ортогональность у них была фальшивой.** Тип записи
(`goal`/`idea`/`task`) и род работы (`kind:<род>` тегом) давали двенадцать клеток (`goal`/`idea`/`task`) и род работы (`kind:<род>` тегом) давали двенадцать клеток
произведения, из которых законны были шесть: у цели род запрещён, у задачи произведения, из которых законны были шесть: у цели род запрещён, у задачи
обязателен, у идеи пуст. Плюс алгоритм работы крепится не к `task`, а к `fix` и обязателен, у идеи пуст. Плюс алгоритм работы крепится не к `task`, а к `fix` и
@@ -301,7 +246,8 @@ stateDiagram-v2
изменения, а не по типу задачи: `chore` бывает миграцией схемы, `fix` — правкой изменения, а не по типу задачи: `chore` бывает миграцией схемы, `fix` — правкой
публичного контракта. Правило «предписание процесса в теле задачи снимается» публичного контракта. Правило «предписание процесса в теле задачи снимается»
типом не отменяется, а подтверждается: он описывает работу, а не то, как её типом не отменяется, а подтверждается: он описывает работу, а не то, как её
проверять. проверять. **Стадия проекта их тоже не выбирает**: изменение на стройке ничем не
проще того же изменения на доработке, и метку ему по-прежнему назначает разметка.
**Одно исполнителю тип всё же говорит — каким сценарием работу вести, и то не **Одно исполнителю тип всё же говорит — каким сценарием работу вести, и то не
один.** Скилл `av-dev:code-resolve` выбирает сценарий связкой из двух один.** Скилл `av-dev:code-resolve` выбирает сценарий связкой из двух
@@ -316,11 +262,10 @@ stateDiagram-v2
Три требования к тексту. Первое — про заголовок, два остальных про то, чтобы Три требования к тексту. Первое — про заголовок, два остальных про то, чтобы
задачу можно было **оценить, не открывая код**. задачу можно было **оценить, не открывая код**.
**Заголовок отвечает на вопрос своего типа.** Вопросов три, поэтому и форм три: **Заголовок отвечает на вопрос своего типа.** Вопросов два, поэтому и форм две:
| Тип | Отвечает на | Пример | | Тип | Отвечает на | Пример |
| --- | --- | --- | | --- | --- | --- |
| 🎯 `goal` | что приложение будет уметь | Соперником может быть компьютер |
| ✨ `feature`, 🐞 `fix`, 🧹 `chore` | что нужно сделать | Печатать поле одним куском кода | | ✨ `feature`, 🐞 `fix`, 🧹 `chore` | что нужно сделать | Печатать поле одним куском кода |
| 🔬 `research` | о чём разведка | Подсказка следующего хода | | 🔬 `research` | о чём разведка | Подсказка следующего хода |
@@ -333,10 +278,6 @@ stateDiagram-v2
исход знание, а что делать — ещё неизвестно, и заголовок-действие обещал бы исход знание, а что делать — ещё неизвестно, и заголовок-действие обещал бы
решённость, которой нет. решённость, которой нет.
Из этого же правила растёт разница индексов: роадмап — список возможностей,
беклог — список работ, и если заголовки перепутать формами, каждый из них
начинает читаться как другой.
`check` считает заголовки не в форме действия и печатает **число** в блоке `check` считает заголовки не в форме действия и печатает **число** в блоке
здоровья, не замечанием на файл: проверка эвристическая (первое слово на здоровья, не замечанием на файл: проверка эвристическая (первое слово на
`-ть`/`-ти`/`-чь`), а беклог, заведённый до правила, не переоформляют «заодно». `-ть`/`-ти`/`-чь`), а беклог, заведённый до правила, не переоформляют «заодно».
@@ -386,18 +327,20 @@ stateDiagram-v2
подкаталога — обычное дело. подкаталога — обычное дело.
``` ```
python3 $tk check --dir D # согласованность индексов + здоровье python3 $tk check --dir D # согласованность индекса + здоровье
python3 $tk check --dir D --fix # + починить дрейф (тип, эмодзи, место, заголовок, дубли, «зачем», форма меты) 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 list --dir D [--stale] [--section S] [--type T] [--tag a,b] [--raw] [--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 add --dir D --slug S --title T --type feature|fix|chore|research [--section S] [--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 edit S --dir D [--title T] [--why «зачем»] [--type T] [--add-tag a,b] [--rm-tag c]
python3 $tk move S --dir D [--section S] [--reason R] [--after S | --first] # без --section — текущая секция 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 --reason R # в REJECTED.md + удалить (ушла без реализации)
python3 $tk close S --dir D --implemented # просто удалить (реализована и закоммичена) python3 $tk close S --dir D --implemented # просто удалить (реализована и закоммичена)
python3 $tk reopen S --dir D --reason R # вернуть закрытую: приёмка не сошлась python3 $tk reopen S --dir D --reason R # вернуть закрытую: приёмка не сошлась
python3 $tk ready S… --dir D # схема типа выполнена — можно брать в работу python3 $tk ready S… --dir D # схема типа выполнена — можно брать в работу
python3 $tk init --dir D [--sections …] [--items …] [--backlog …] … python3 $tk stage --dir D # показать стадию
python3 $tk adopt scan --from … | apply --plan … # разовая адаптация, references/adopt.md python3 $tk stage support --dir D [--sections] # сменить стадию: секции и смысл порядка
python3 $tk init --dir D --stage build|support [--sections …] [--items …] …
python3 $tk adopt scan --from … --stage S | apply --plan … # разовая адаптация, references/adopt.md
``` ```
**Копия.** Дом словаря — `shared/axes.md` в репозитории плагина: словарь общий **Копия.** Дом словаря — `shared/axes.md` в репозитории плагина: словарь общий
@@ -422,35 +365,32 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
<!-- /копия: коды-выхода --> <!-- /копия: коды-выхода -->
Здесь это значит: код 1 приходит **только от `check`** — найден дрейф индексов и Здесь это значит: код 1 приходит **только от `check`** — найден дрейф индекса и
файлов, чинится `check --fix`, остаток разбирается руками. Код 3 — каталог не файлов, чинится `check --fix`, остаток разбирается руками. Код 3 — каталог не
найден, конфиг битый или мимо диска: чинится путём или `.av-dev.toml` в корне. найден, конфиг битый или мимо диска: чинится путём или `.av-dev.toml` в корне.
Тип — английское ключевое слово `goal` / `feature` / `fix` / `chore` / Тип — английское ключевое слово `feature` / `fix` / `chore` / `research` (как и
`research` (как и прочие токены команд), у `add` **обязательное**: без него прочие токены команд), у `add` **обязательное**: без него неизвестно, какой
неизвестно, какой шаблон тела класть. Текст задачи при этом русский, а эмодзи в шаблон тела класть. Стадия — такое же слово, `build` / `support`, и у `init` она
заголовке ставит скрипт. обязательна по той же причине: без неё неизвестно, что писать в шапке беклога и
сколько заводить секций. Текст задачи при этом русский, а эмодзи в заголовке
ставит скрипт.
**Мутации правят файл и индексы заодно** — руками строку индекса или мету **Мутации правят файл и индекс заодно** — руками строку индекса или мету
не пиши, зови `add`/`edit`/`move`/`close`/`reopen`. Смена заголовка, «зачем», типа, не пиши, зови `add`/`edit`/`move`/`close`/`reopen`. Смена заголовка, «зачем», типа
цели и **тегов** — это `edit`: он держит H1 (вместе с эмодзи), мету и индекс и **тегов** — это `edit`: он держит H1 (вместе с эмодзи), мету и индекс
согласованными. Снятие тега — `--rm-tag` (после ответа на вопрос снимается согласованными. Снятие тега — `--rm-tag` (после ответа на вопрос снимается
`question`), смена цели — `--goal`, типа — `--type`; оба заменяют прежнее `question`), смена типа — `--type`; оба заменяют прежнее значение, а не
значение, а не добавляют второе. добавляют второе.
**Переезд между индексами — следствие смены типа, а не отдельная команда.** **Секцию меняет только `move`, и он пишет причину**: смена полки без причины и
`edit <slug> --type goal --section <часть роадмапа>` переносит строку из есть тот дрейф, который потом никто не объяснит.
`BACKLOG.md` в `ROADMAP.md` (и обратно — задачным типом плюс
`--section <категория беклога>`);
`move` двигает только внутри одного индекса и пишет причину. `--section` у
`edit` работает **только** при таком переезде — иначе он отсылает к `move`,
потому что смена секции без причины и есть тот дрейф, который потом никто не
объяснит.
**`move --after <слаг>` и `move --first` — это и есть расстановка приоритета.** **`move --after <слаг>` и `move --first` — это и есть расстановка порядка.**
Порядок строк в секции значим (правило 4), и двигают его только этой командой: Порядок строк в секции значим (правило 4), и двигают его только этой командой:
руками поправленная строка не оставляет причины, а причина здесь и есть половина руками поправленная строка не оставляет причины, а причина здесь и есть половина
решения. решения. Что именно этот порядок значит, говорит стадия: на стройке `--after`
называет зависимость, на доработке — приоритет.
Тело задачи скрипт не трогает: Тело задачи скрипт не трогает:
`add` кладёт заголовок, мета-блок и шаблон с подсказками, тело дописываешь `add` кладёт заголовок, мета-блок и шаблон с подсказками, тело дописываешь
@@ -461,18 +401,23 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
правки**, даже если правил мутациями: дрейф мог накопиться раньше. Накопившееся правки**, даже если правил мутациями: дрейф мог накопиться раньше. Накопившееся
чини `check --fix` — он детерминированно правит то, где истина однозначна (тип в чини `check --fix` — он детерминированно правит то, где истина однозначна (тип в
своё поле, эмодзи заголовка, имя поля места, секция, заголовок, дубли, «зачем» из своё поле, эмодзи заголовка, имя поля места, секция, заголовок, дубли, «зачем» из
индекса в файл, старая форма меты, пометка `decomposed` у цели с задачами, сырьё индекса в файл, старая форма меты, снятые теги упразднённых целей, сырьё
в конец категории), а неоднозначное (задача сразу в двух индексах, нечего в конец секции), а неоднозначное (нечего восстанавливать, **тип, которого
восстанавливать, **тип, которого неоткуда взять**) печатает отдельной пометкой неоткуда взять**, запись типа `goal`) печатает отдельной пометкой
`НЕОДНОЗНАЧНО` — это тебе, и это идёт строкой доклада. **Ссылка на исчезнувший `НЕОДНОЗНАЧНО` — это тебе, и это идёт строкой доклада. **Ссылка на исчезнувший
файл в пометку не попадает:** `--fix` её просто не трогает, и она остаётся файл в пометку не попадает:** `--fix` её просто не трогает, и она остаётся
`ОШИБКА` обычного `check` — то есть видна, но в докладе её надо назвать отдельно. `ОШИБКА` обычного `check` — то есть видна, но в докладе её надо назвать отдельно.
`--fix` правит **и файлы** — там, где источник ровно один и выбирать не из чего: `--fix` правит **и файлы** — там, где источник ровно один и выбирать не из чего:
тип переезжает из прежнего дома (тег `kind:`, префикс `[goal]`/`[idea]`) в поле тип переезжает из прежнего дома (тег `kind:`, префикс `[goal]`/`[idea]`) в поле
меты, заголовок получает эмодзи, поле места — имя по типу, «зачем», оставшееся меты, заголовок получает эмодзи, поле места зовётся «Категория», «зачем»,
только в индексе, переезжает в мету, цель с задачами получает `decomposed`. оставшееся только в индексе, переезжает в мету, теги `goal:` и `decomposed`
Каждый случай печатается поимённо. снимаются. Каждый случай печатается поимённо.
**Ни стадию, ни состав секций `--fix` не трогает.** Стадию он подставить не
может — это решение человека; секции стройки не сливает — в каком порядке пойдут
строки слитых полок, знает тоже только человек, а порядок здесь и есть
содержание.
**Тип, который не выводится ниоткуда, `--fix` не угадывает.** `feature` от **Тип, который не выводится ниоткуда, `--fix` не угадывает.** `feature` от
`chore` машина не отличает, и подставленное наугад значение врало бы ровно там, `chore` машина не отличает, и подставленное наугад значение врало бы ровно там,
@@ -483,7 +428,7 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
работу — там, где по ней принимают решение; `check` поимённо о ней не говорит, а работу — там, где по ней принимают решение; `check` поимённо о ней не говорит, а
считает: строка здоровья **«схема типа не выполнена: N из M»** называет число и считает: строка здоровья **«схема типа не выполнена: N из M»** называет число и
первые слаги, строка **«готово к взятию»** — сколько задач беклога пройдут первые слаги, строка **«готово к взятию»** — сколько задач беклога пройдут
`ready` целиком (схема плюс цель плюс отсутствие открытого вопроса). Это две `ready` целиком (схема плюс отсутствие открытого вопроса). Это две
разные строки, и совпадение их чисел — совпадение. У каждой части своя глубина: разные строки, и совпадение их чисел — совпадение. У каждой части своя глубина:
- **тип** — жёстко: назван и из закрытого словаря; - **тип** — жёстко: назван и из закрытого словаря;
@@ -491,7 +436,7 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
(меньше двух отказ, больше пяти замечание), наличие оракула **эвристикой** по (меньше двух отказ, больше пяти замечание), наличие оракула **эвристикой** по
слову «оракул» в пункте; слову «оракул» в пункте;
- **прочие разделы схемы** (`Затрагивает`, `Воспроизведение`, `Вопрос`, - **прочие разделы схемы** (`Затрагивает`, `Воспроизведение`, `Вопрос`,
`Куда ляжет ответ`, `Завершение`) — только **наличие непустого**. Содержимое `Куда ляжет ответ`) — только **наличие непустого**. Содержимое
машине не видно: границу, которую забыли назвать, она от отсутствующей не машине не видно: границу, которую забыли назвать, она от отсутствующей не
отличает, а шаги, по которым ничего не воспроизводится, — от годных. отличает, а шаги, по которым ничего не воспроизводится, — от годных.
@@ -500,10 +445,10 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
разделов своего типа и число критериев, годность оракулов и полнота границ — разделов своего типа и число критериев, годность оракулов и полнота границ —
глазами». глазами».
Формат записи, меты, слага, индексов и `REJECTED.md` Формат записи, меты, слага, индекса и `REJECTED.md`
[references/task-format.md](references/task-format.md); там же тест «готова к [references/task-format.md](references/task-format.md); там же тест «готова к
взятию». Схема и алгоритм каждого типа — по файлу на тип: взятию». Схема и алгоритм каждого типа — по файлу на тип:
[goal](references/task-goal.md) · [feature](references/task-feature.md) · [feature](references/task-feature.md) ·
[fix](references/task-fix.md) · [chore](references/task-chore.md) · [fix](references/task-fix.md) · [chore](references/task-chore.md) ·
[research](references/task-research.md). [research](references/task-research.md).
@@ -530,9 +475,13 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
### Завести запись из диалога ### Завести запись из диалога
1. **Фильтр.** Делаем прямо сейчас — не заводим. Не пожалеем о потере — не 0. **Посмотри стадию**`stage`. От неё зависят шаг 1 и место новой строки: на
заводим. Родилось три кандидата — покажи их и спроси, какие заводить: молча доработке беклог пополняют по одной и с фильтром, на стройке пишут планом.
заведённая пачка и есть тот самый отказ из правила 1. 1. **Фильтр — на доработке.** Делаем прямо сейчас — не заводим. Не пожалеем о
потере — не заводим. Родилось три кандидата — покажи их и спроси, какие
заводить: молча заведённая пачка и есть тот самый отказ из правила 1.
**На стройке фильтра нет**: план пишется вперёд целиком, и «этого мы сейчас
не делаем» — не довод против шага, а описание всякого шага, кроме первого.
2. **Дедуп.** `list` плюс поиск по слагам, полю «зачем» и телам (`grep -ril`), 2. **Дедуп.** `list` плюс поиск по слагам, полю «зачем» и телам (`grep -ril`),
**включая `REJECTED.md`**. Нашлось среди живых — **дописываем в существующий **включая `REJECTED.md`**. Нашлось среди живых — **дописываем в существующий
файл**, а не заводим соседний. Нашлось в `REJECTED.md` — покажи пользователю файл**, а не заводим соседний. Нашлось в `REJECTED.md` — покажи пользователю
@@ -541,7 +490,6 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
переоценки. переоценки.
3. **Тип**`--type` обязателен, и он же первое содержательное решение: 3. **Тип**`--type` обязателен, и он же первое содержательное решение:
- возможность приложения, а не шаг к ней → `goal`;
- снаружи появляется то, чего не было → `feature`; - снаружи появляется то, чего не было → `feature`;
- поведение расходится с заявленным и **воспроизводится**`fix` - поведение расходится с заявленным и **воспроизводится**`fix`
(не воспроизводится → `research`); (не воспроизводится → `research`);
@@ -550,12 +498,12 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
Не подходит ни один — в записи их два, разбирай. Не проходит тест готовности Не подходит ни один — в записи их два, разбирай. Не проходит тест готовности
(см. task-format) — это **сырьё**: `--type research`, раздел «Вопрос» пока (см. task-format) — это **сырьё**: `--type research`, раздел «Вопрос» пока
пуст, место в конце категории. Не делается одним заходом — это не эпик, а пуст, место в конце секции. Не делается одним заходом — дроби на шаги
несколько задач под одной целью: дроби сразу. помельче и ставь их в списке подряд.
4. **Цель — если тип её требует.** У `feature` должен быть `--goal <слаг>`: 4. **Место в списке.** `add` кладёт строку в конец секции всегда. На стройке это
новая возможность и есть содержание цели. Подходящей нет — либо она почти наверняка не то место: порядок там зависимость, и новый шаг чаще всего
заводится (`--type goal`), либо перед тобой не `feature`. У `fix`, `chore` и встаёт в середину — `move <слаг> --after <слаг>`. На доработке конец списка
`research` цели может не быть вовсе, и придумывать её не надо. законен: место в очереди назначает груминг, а не заведение.
5. `add …`, затем допиши тело редактором **по схеме своего типа** — шаблон её 5. `add …`, затем допиши тело редактором **по схеме своего типа** — шаблон её
уже разложил, устав типа объясняет каждый раздел. «Зачем» отвечает «зачем уже разложил, устав типа объясняет каждый раздел. «Зачем» отвечает «зачем
нужна эта задача» — состояние, остаток, боль, — а не пересказывает первый нужна эта задача» — состояние, остаток, боль, — а не пересказывает первый
@@ -569,13 +517,13 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
пять файлов из одной мысли, а сорок файлов из сорока сырых находок. Защита та пять файлов из одной мысли, а сорок файлов из сорока сырых находок. Защита та
же, что в самом ревью: кластеризация по причине, дедуп против живых и же, что в самом ревью: кластеризация по причине, дедуп против живых и
`REJECTED.md`, находка без свидетельства → сырьё (`research`), а не задача, и карта кластеров `REJECTED.md`, находка без свидетельства → сырьё (`research`), а не задача, и карта кластеров
пользователю до создания файлов. Порядок, отображение серьёзности и привязка к пользователю до создания файлов. Порядок и отображение серьёзности
целям — [references/from-review.md](references/from-review.md). [references/from-review.md](references/from-review.md).
### Прийти в репозиторий, где задачи уже как-то ведутся ### Прийти в репозиторий, где задачи уже как-то ведутся
Разовая операция: вывести каталог задач из старой раскладки беклога, `TODO.md`, Разовая операция: вывести каталог задач из старой раскладки беклога, `TODO.md`,
заметок или списка шагов роадмапа — [references/adopt.md](references/adopt.md). заметок или списка шагов плана — [references/adopt.md](references/adopt.md).
Сюда же относится переименование транслитных слагов в английские: оно делается Сюда же относится переименование транслитных слагов в английские: оно делается
**одним проходом вместе с починкой перекрёстных ссылок**, а не по одному слагу. **одним проходом вместе с починкой перекрёстных ссылок**, а не по одному слагу.
@@ -601,13 +549,12 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
| Проход | Что смотрит | Над чем работает | | Проход | Что смотрит | Над чем работает |
| --- | --- | --- | | --- | --- | --- |
| `task-form` | заголовок по типу, «зачем» вместо пересказа, границы вместо замысла, годность оракулов, предписание процесса, связь со строкой «Завершения» цели | только `items/`, **открывает файл цели** | | `task-form` | заголовок по типу, «зачем» вместо пересказа, границы вместо замысла, годность оракулов, предписание процесса | только `items/` |
| `task-wording` | залог и отглагольные, оценка без факта, стоп-слова, англицизмы, жаргон, неизвестный термин, транслит в слаге | `items/` и строки индексов; документы проекта — только как словарь | | `task-wording` | залог и отглагольные, оценка без факта, стоп-слова, англицизмы, жаргон, неизвестный термин, транслит в слаге | `items/` и строки индекса; документы проекта — только как словарь |
Разделены они не по охвату, а **по глубине**. Язык проверяется по словам и Разделены они не по охвату, а **по глубине**. Язык проверяется по словам и
фразам, поштучно; форма записи требует понять, что задача делает, и открыть фразам, поштучно; форма записи требует понять, что задача делает. Слитый проход
цель, на которую она ссылается. Слитый проход одну половину делает дорогой, а одну половину делает дорогой, а вторую — поверхностной.
вторую — поверхностной.
Модель у обоих одна, `sonnet`, и это не отменяет разреза. Оба судят по Модель у обоих одна, `sonnet`, и это не отменяет разреза. Оба судят по
**записанному правилу** — семь пунктов формы против правил языка, — а их находка **записанному правилу** — семь пунктов формы против правил языка, — а их находка
@@ -690,18 +637,20 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
полагаться на него скилл не должен: молча найденный чужой каталог это дрейф. полагаться на него скилл не должен: молча найденный чужой каталог это дрейф.
- **Версия и настройки живут в `.av-dev.toml` в корне репозитория** — версия - **Версия и настройки живут в `.av-dev.toml` в корне репозитория** — версия
ключом `version`, настройки каталога секцией `[tasks]`: `dir` — где каталог ключом `version`, настройки каталога секцией `[tasks]`: `dir` — где каталог
лежит, плюс **имена** файлов и заголовков, и последние только если отличаются лежит, `stage` — стадия проекта, плюс **имена** файлов и заголовков, и
от умолчания. Неизвестный ключ в секции — код 3 на любой команде, так что последние только если отличаются от умолчания. Неизвестный ключ в секции — код
лишнее слово останавливает работу с задачами целиком. 3 на любой команде, так что лишнее слово останавливает работу с задачами
целиком.
Дом в корне, а не внутри каталога задач, по двум причинам: настройка, лежащая Дом в корне, а не внутри каталога задач, по двум причинам: настройка, лежащая
внутри настраиваемого каталога, не смогла бы сказать, **где он**; и версия внутри настраиваемого каталога, не смогла бы сказать, **где он**; и версия
одна на весь плагин, а корень есть и у проекта без `docs/`. Прежние одна на весь плагин, а корень есть и у проекта без `docs/`. Прежние
`<каталог задач>/.tasks.json` и `docs/.docs.json` не читаются — увидев их, `<каталог задач>/.tasks.json` и `docs/.docs.json` не читаются — увидев их,
скрипт говорит «прежняя раскладка» и зовёт `upgrade`. скрипт говорит «прежняя раскладка» и зовёт `upgrade`.
- **Секции беклога** берутся из заголовков `##` индекса как есть; их количество - **Секции беклога** берутся из заголовков `##` индекса как есть; их названия —
и названия — дело проекта (умолчание `Ядро` / `Инфра`). **В конфиге их нет** дело проекта (умолчание `План` на стройке, `Ядро` / `Инфра` на доработке), а
второй список разошёлся бы с заголовками молча. количество ограничено стадией: на стройке секция одна. **В конфиге секций
нет** — второй список разошёлся бы с заголовками молча.
### Вызов из другого плагина ### Вызов из другого плагина
@@ -738,8 +687,10 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
- **Развилки — пользователю.** Через `AskUserQuestion`, с уже сформулированным - **Развилки — пользователю.** Через `AskUserQuestion`, с уже сформулированным
предварительным суждением (**рекомендация — первым вариантом**). Что выкинуть, предварительным суждением (**рекомендация — первым вариантом**). Что выкинуть,
под какую цель отнести, какая рамка разведки верна — решение пользователя. Слаг, какая рамка разведки верна, пора ли менять стадию — решение пользователя. Слаг
формулировка, порядок строк в индексе — механика, делаем сами. и формулировка — механика, делаем сами. **Порядок строк механикой не
считается** ни на одной стадии: на стройке он зависимость, на доработке
приоритет, и оба называет человек.
- **Не больше трёх вопросов за раз.** Пачка длиннее трёх тяжела для ответа; - **Не больше трёх вопросов за раз.** Пачка длиннее трёх тяжела для ответа;
решений больше — веди **несколько итераций** диалога по ≤3, а не один решений больше — веди **несколько итераций** диалога по ≤3, а не один
перегруженный запрос. Между итерациями применяй уже решённое. перегруженный запрос. Между итерациями применяй уже решённое.
+48 -42
View File
@@ -1,7 +1,7 @@
# Адаптация каталога задач # Адаптация каталога задач
Проект, где задачи уже как-то ведутся, и из имеющегося материала **выводится** Проект, где задачи уже как-то ведутся, и из имеющегося материала **выводится**
заполненный каталог задач: цели, задачи, кладбище, индексы. Операция разовая — заполненный каталог задач: задачи, кладбище, индекс. Операция разовая —
после неё проект живёт скиллами `task-track` и `task-groom`. после неё проект живёт скиллами `task-track` и `task-groom`.
**Это часть приведения проекта к канону.** Раскладку `docs/` целиком ведёт скилл **Это часть приведения проекта к канону.** Раскладку `docs/` целиком ведёт скилл
@@ -12,12 +12,12 @@
Вход какой угодно: старая раскладка `av-dev-backlog` (индекс `README.md`, Вход какой угодно: старая раскладка `av-dev-backlog` (индекс `README.md`,
кладбище `CLOSED.md`, приоритеты секциями, транслитные слаги, файлы рядом с кладбище `CLOSED.md`, приоритеты секциями, транслитные слаги, файлы рядом с
индексом), `TODO.md`, россыпь заметок, раздел «планы» в `README.md`, список индексом), `TODO.md`, россыпь заметок, раздел «планы» в `README.md`, список
шагов роадмапа проекта. шагов плана проекта.
## Три правила, из которых всё следует ## Три правила, из которых всё следует
1. **Сперва карта, потом файлы.** Человеку показывается, что найдено, как 1. **Сперва карта, потом файлы.** Человеку показывается, что найдено, в каком
разложилось по целям и **что не разложилось**, — и только после подтверждения порядке разложилось и **что не разложилось**, — и только после подтверждения
пишется хоть один файл. Это то же правило, что у интейка находок ревью: пишется хоть один файл. Это то же правило, что у интейка находок ревью:
массовое заведение записей без подтверждения — самый дорогой отказ, потому массовое заведение записей без подтверждения — самый дорогой отказ, потому
что разгребает его потом переоценка. что разгребает его потом переоценка.
@@ -38,7 +38,7 @@
tk="$CLAUDE_PLUGIN_ROOT/skills/task-track/scripts/tasks.py" tk="$CLAUDE_PLUGIN_ROOT/skills/task-track/scripts/tasks.py"
python3 $tk adopt scan --from docs/backlog docs/plan.md TODO.md \ python3 $tk adopt scan --from docs/backlog docs/plan.md TODO.md \
--target tasks --out tasks-adopt-plan.json # только чтение --stage build --target tasks --out tasks-adopt-plan.json # только чтение
python3 $tk adopt apply --plan tasks-adopt-plan.json \ python3 $tk adopt apply --plan tasks-adopt-plan.json \
--refs docs openspec CLAUDE.md README.md # запись --refs docs openspec CLAUDE.md README.md # запись
``` ```
@@ -53,56 +53,60 @@ python3 $tk adopt apply --plan tasks-adopt-plan.json \
- **английские слаги.** Перевести `taj-brejk-pri-ravnoj-polnote` в - **английские слаги.** Перевести `taj-brejk-pri-ravnoj-polnote` в
`tie-break-equal-completeness` может только тот, кто понимает смысл. `scan` `tie-break-equal-completeness` может только тот, кто понимает смысл. `scan`
честно говорит: проверить надо **все** слаги, признаки транслита — эвристика; честно говорит: проверить надо **все** слаги, признаки транслита — эвристика;
- **цели.** Шаги роадмапа — готовые цели в **`Запланировано`** (очередь и - **стадия.** `--stage` называет, чем этот беклог будет: планом стройки или
обоснование у них уже есть); тематические скопления задач — цели в очередью правок. Машине это не выводится — она видит список пунктов, а не то,
**`Направления`** («прочность слияния», построено приложение или нет;
«журнал и пересборка»). Предлагаешь ты, назначает человек; - **порядок.** Нумерованные шаги источника `scan` сохраняет по номерам, прочие
ставит следом. Дальше порядок — твоё суждение и подтверждение человека: на
стройке это зависимость, на доработке важность;
- **что вообще не задача.** Обоснование порядка шагов, абзац прозой, заголовок - **что вообще не задача.** Обоснование порядка шагов, абзац прозой, заголовок
раздела — это не пункты беклога, и они уходят в «не разложилось» с причиной. раздела — это не пункты беклога, и они уходят в «не разложилось» с причиной.
## Порядок ## Порядок
1. **Осмотрись.** Где лежат задачи, роадмап, заметки. Каталог задач по канону — 1. **Осмотрись и назови стадию.** Где лежат задачи, план, заметки; построено
всегда `tasks`. Секции беклога (`--sections`) — по умолчанию приложение или строится. Каталог задач по канону — всегда `tasks`. Секции
`Ядро,Инфра`; если у проекта деление другое по существу, оно называется беклога (`--sections`) — на стройке ровно одна (умолчание `План`), на
здесь, а не подгоняется под умолчание, и становится **заголовками `##` доработке сколько нужно (умолчание `Ядро,Инфра`); если у проекта деление
индекса** — их единственным домом. В `.av-dev.toml` секции не пишутся: там другое по существу, оно называется здесь, а не подгоняется под умолчание, и
версия формата и имена частей, а второй список секций разошёлся бы с становится **заголовками `##` индекса** — их единственным домом. В
заголовками молча. `.av-dev.toml` секции не пишутся: там версия, стадия и имена частей, а второй
список секций разошёлся бы с заголовками молча.
2. **`adopt scan`** по всем источникам разом. Один прогон, одна карта: два 2. **`adopt scan`** по всем источникам разом. Один прогон, одна карта: два
прохода дадут два несогласованных состояния. прохода дадут два несогласованных состояния.
3. **Заполни карту**: `slug` (английский), `section`, `goal` у каждой записи; 3. **Заполни карту**: `slug` (английский), `type` и `section` у каждой записи, и
список `goals` — из шагов роадмапа и из тем. Закрытый шаг целью не **порядок `items`** — он уедет в индекс как есть. Пункт, помеченный
заводится. Пустой `goal` законен у `fix`, `chore` и `research` — они служат закрытым, не переносится вовсе.
работоспособности, а не направлению; у `feature` цель обязательна.
4. **Покажи человеку карту** через `AskUserQuestion`, ≤3 вопроса за итерацию, 4. **Покажи человеку карту** через `AskUserQuestion`, ≤3 вопроса за итерацию,
рекомендация первым вариантом. Показывается: сколько записей, предлагаемые рекомендация первым вариантом. Показывается: сколько записей, предлагаемый
цели (порядок и темы) с обоснованием, спорные отнесения, список «не порядок с обоснованием, спорные отнесения, список «не разложилось». Слаги не
разложилось». Массовые механические решения (слаги, порядок строк) не выносятся — это механика; **порядок выносится всегда**, потому что механикой
выносятся — это механика. он не является ни на одной стадии.
5. **`adopt apply`.** `--refs` перечисляет **всё**, где могут стоять ссылки на 5. **`adopt apply`.** `--refs` перечисляет **всё**, где могут стоять ссылки на
слаги: документация, архив изменений, `CLAUDE.md`, `README.md`. Скрипт слаги: документация, архив изменений, `CLAUDE.md`, `README.md`. Скрипт
посчитает и покажет, сколько ссылок поправлено и по каким слагам. посчитает и покажет, сколько ссылок поправлено и по каким слагам.
6. **`tasks.py check`** и доклад. 6. **`tasks.py check`** и доклад.
`apply` отказывается писать поверх живого каталога и проверяет карту целиком `apply` отказывается писать поверх живого каталога и проверяет карту целиком
**до** первой записи: неверная секция, дубль слага, цель, которой нет в карте — **до** первой записи: неверная секция, дубль слага, неназванный тип, две секции
всё это отказ до того, как на диске появился хотя бы один файл. при стадии `build`всё это отказ до того, как на диске появился хотя бы один
файл.
## Переходное состояние — объявляется, а не заминается ## Переходное состояние — объявляется, а не заминается
Сразу после адаптации задачи в большинстве своём **не готовы к взятию**: у них Сразу после адаптации задачи в большинстве своём **не готовы к взятию**: у них
нет критериев приёмки, а у части может не быть цели. Это нормально, но обязано нет критериев приёмки. Это нормально, но обязано быть названо, иначе следующий
быть названо, иначе следующий агент примет пустой беклог за поломку. агент примет пустой беклог за поломку.
`apply` печатает состояние по факту: сколько задач без цели (это **ошибки** `apply` печатает состояние по факту: сколько задач не собрало разделы своего типа
`check`) и сколько не собрало разделы своего типа (для `check` это не ошибка, а (для `check` это не ошибка, а строка здоровья, но `ready` такую задачу не
строка здоровья, но `ready` такую задачу не пропустит). Закрывается это пропустит). Закрывается это **порциями груминга** — скилл `groom`, 58 задач за
**порциями груминга** — скилл порцию: превратить «готово, когда» в критерии с оракулами, вынуть вопросы из
`groom`, 5–8 задач за порцию: проставить цели, превратить «готово, когда» в прозы в раздел «Вопросы».
критерии с оракулами, вынуть вопросы из прозы в раздел «Вопросы». Там же
беклогу впервые назначается **порядок**: после адаптации его нет вовсе, а **Порядок строк проверяется глазами отдельно.** На стройке он выведен из
очередь и есть то, ради чего каталог заводят. нумерации источника, и там, где её не было, он случаен. На доработке машина
важности не знает вовсе — очередь расставляется первым же грумингом.
Готовность к первой задаче — не «`check` зелёный», а «`ready` пропускает хотя бы Готовность к первой задаче — не «`check` зелёный», а «`ready` пропускает хотя бы
верхние строки очереди». верхние строки очереди».
@@ -113,18 +117,20 @@ python3 $tk adopt apply --plan tasks-adopt-plan.json \
дело человека, удалять чужое молча нельзя. В доклад идёт готовая команда. дело человека, удалять чужое молча нельзя. В доклад идёт готовая команда.
- **Не переписывает подписи ссылок.** `[docs/backlog](tasks/BACKLOG.md)` - **Не переписывает подписи ссылок.** `[docs/backlog](tasks/BACKLOG.md)`
цель поправлена, текст остался; это правится глазами, и таких мест немного. цель поправлена, текст остался; это правится глазами, и таких мест немного.
- **Не сочиняет критерии приёмки и не придумывает цели**, которых в материале - **Не сочиняет критерии приёмки** и не додумывает пользы, которой в материале
нет. Придуманная цель хуже отсутствующей: под неё заведут задачи. нет.
- **Не выводит стадию.** Список пунктов одинаково выглядит и планом стройки, и
очередью правок; отвечает `--stage`, а называет его человек.
- **Не трогает историю.** В коммитах старые слаги остаются, и это нормально. - **Не трогает историю.** В коммитах старые слаги остаются, и это нормально.
## Доклад ## Доклад
- Источники и что в каждом распознано (раскладка, индекс, кладбище, секции). - Источники и что в каждом распознано (раскладка, индекс, кладбище, секции).
- Сколько записей перенесено, сколько целей заведено (порядок / темы) и откуда - Стадия и сколько записей перенесено; откуда взялся порядок (нумерация
каждая выведена. источника или суждение).
- **Переименования**: сколько слагов, сколько ссылок поправлено и в скольких - **Переименования**: сколько слагов, сколько ссылок поправлено и в скольких
файлах — числом, а не «поправлены ссылки». файлах — числом, а не «поправлены ссылки».
- **Не разложилось**: поимённо, с причиной. - **Не разложилось**: поимённо, с причиной.
- Переходное состояние: сколько задач без цели, сколько без критериев, чем и за - Переходное состояние: сколько задач без критериев, чем и за сколько порций
сколько порций закрывается. закрывается.
- `tasks.py check` — результат строкой. - `tasks.py check` — результат строкой.
@@ -50,17 +50,12 @@
заведённое и уже выкинутое. Нашлось среди живых — дописываем находку в заведённое и уже выкинутое. Нашлось среди живых — дописываем находку в
существующий файл. Нашлось в `REJECTED.md` — это сигнал: причина отказа могла существующий файл. Нашлось в `REJECTED.md` — это сигнал: причина отказа могла
устареть, выноси пользователю, а не заводи молча заново. устареть, выноси пользователю, а не заводи молча заново.
4. **Разложи по целям — там, где цель нужна.** Большинство находок ревью это 4. **Проставь типы.** Большинство находок ревью это `fix` и `chore`. Находка,
`fix` и `chore`, и **цель им не требуется**: они служат работоспособности, а оказавшаяся **новой возможностью** (`feature`), — отдельный случай: нашлось
не направлению. Придуманная им цель — поведение, которого никто не заказывал, и решение тут не «завести задачу», а
ровно то враньё, от которого спасает тип. «заказать или убрать». Выноси такую пользователю отдельно от прочих.
Цель обязательна у находки, которая оказалась **новой возможностью**
(`feature`): нашлось поведение, которого никто не заказывал, и его надо
либо заказать целью, либо убрать. Подходящей цели нет — заведи её
(`add --type goal --section Направления`) в том же проходе.
5. **Покажи карту до создания файлов.** Кластер → задача / сырьё / строка в 5. **Покажи карту до создания файлов.** Кластер → задача / сырьё / строка в
пакетный файл / уже заведено / отброшено, и под какую цель — пачкой через пакетный файл / уже заведено / отброшено — пачкой через
`AskUserQuestion`. Это тот же барьер, что и «три кандидата» в интейке из `AskUserQuestion`. Это тот же барьер, что и «три кандидата» в интейке из
диалога: массовое заведение файлов без подтверждения — ровно тот отказ, ради диалога: массовое заведение файлов без подтверждения — ровно тот отказ, ради
которого интейк из ревью и выделен. Дешёвая мелочь по явному согласию может которого интейк из ревью и выделен. Дешёвая мелочь по явному согласию может
@@ -88,8 +83,8 @@
[скилле груминга](../../task-groom/SKILL.md#приоритет-как-его-расставляют), и [скилле груминга](../../task-groom/SKILL.md#приоритет-как-его-расставляют), и
серьёзность попадает ровно в один из них. серьёзность попадает ровно в один из них.
- **тяжёлая находка со свидетельством о сломанном сейчас** → задача под ту цель, - **тяжёлая находка со свидетельством о сломанном сейчас** → задача
которой она угрожает, и **первой строкой секции**: `move <слаг> --first **первой строкой секции**: `move <слаг> --first
--reason «сломано сейчас: …»`. Это довод «что сломано сейчас» из перечня --reason «сломано сейчас: …»`. Это довод «что сломано сейчас» из перечня
груминга — единственный, который не требует сравнения с соседями по очереди, груминга — единственный, который не требует сравнения с соседями по очереди,
потому что сломанное дорожает само. Позицию всё равно назначает человек, и потому что сломанное дорожает само. Позицию всё равно назначает человек, и
@@ -124,7 +119,7 @@
## Доклад ## Доклад
- Источник (какое ревью/аудит, сколько находок на входе). - Источник (какое ревью/аудит, сколько находок на входе).
- Свёрнуто в задачи: N кластеров из M находок, со слагами, целями и тегом партии. - Свёрнуто в задачи: N кластеров из M находок, со слагами и тегом партии.
- Что не заведено и почему: починено инлайн, уже заведено, стало сырьём, ушло в - Что не заведено и почему: починено инлайн, уже заведено, стало сырьём, ушло в
`REJECTED.md`. `REJECTED.md`.
- Поимённая сверка: находок на входе N, исход есть у N. - Поимённая сверка: находок на входе N, исход есть у N.
+29 -32
View File
@@ -8,14 +8,19 @@
Задачу можно дробить, только если части удовлетворяют **обоим** условиям: Задачу можно дробить, только если части удовлетворяют **обоим** условиям:
1. **Мерджатся независимо.** Часть Б не требует, чтобы часть А была уже влита. 1. **Каждая мерджится сама по себе.** Часть, после которой дерево не собирается
Есть порядок «сперва А, потом Б, иначе не собрать» → это не декомпозиция, а или поведение сломано до прихода соседней, — не часть, а половина.
план реализации: шаги остаются **внутри одного файла**.
2. **Каждая — самостоятельный шаг.** Часть, осмысленная только в комплекте с 2. **Каждая — самостоятельный шаг.** Часть, осмысленная только в комплекте с
другой, — не задача. Проверяй тестом «готова к взятию» (task-format): какую другой, — не задача. Проверяй тестом «готова к взятию» (task-format): свои
строку «Завершения» цели двигает **именно эта часть** и какие у неё критерии приёмки у неё есть или нет.
собственные критерии приёмки. У операционных частей (`fix`, `chore`,
`research`) цели может не быть — тогда достаточно собственных критериев. **Порядок между частями законен на стройке и подозрителен на доработке**, и это
единственное, что стадия здесь меняет. Беклог стройки **весь** состоит из
упорядоченных зависимостью шагов: «сперва А, потом Б» — не повод не дробить, а
описание того, как этот список устроен, и части просто встают подряд. На
доработке правки независимы, и обнаруженный порядок «иначе не собрать» чаще
всего значит, что перед тобой не декомпозиция, а план реализации: шаги остаются
**внутри одного файла**.
Не проходит хотя бы одно — **не дроби**. Ложная декомпозиция плодит файлы, Не проходит хотя бы одно — **не дроби**. Ложная декомпозиция плодит файлы,
которые нельзя взять поодиночке, и переоценка потом склеивает их обратно. которые нельзя взять поодиночке, и переоценка потом склеивает их обратно.
@@ -45,37 +50,29 @@
гигиена полей снимает. Шов пользуется меткой как **признаком**, что в задаче гигиена полей снимает. Шов пользуется меткой как **признаком**, что в задаче
две разнородные работы; решение о метке остаётся за конвейером. две разнородные работы; решение о метке остаётся за конвейером.
**Цель наследуется.** Все части несут `goal:` родителя: декомпозиция не меняет
того, чему работа служит. Если у части цель другая — это признак, что дробили не
по той границе, либо что часть вообще из другой работы.
## Что делать с родителем ## Что делать с родителем
После разделения родитель **не остаётся** третьей висящей строкой: После разделения родитель **не остаётся** третьей висящей строкой:
- части полностью замещают его → `close <slug> --reason "разложена на a, b"`. части полностью замещают его → `close <slug> --reason "разложена на a, b"`.
`REJECTED.md` здесь — не «выкинули», а именно тот след, что переживает запись: `REJECTED.md` здесь — не «выкинули», а именно тот след, что переживает запись:
через квартал вопрос «куда делась задача X» отвечается строкой со ссылками на через квартал вопрос «куда делась задача X» отвечается строкой со ссылками на
наследников, а не археологией git; наследников, а не археологией git.
- родитель осмыслен как **возможность**, а не как шаг → это цель, и **строка
переезжает**: `edit <slug> --type goal --section <часть роадмапа>` снимает её с
`BACKLOG.md` и вставляет в `ROADMAP.md`. Файл в `items/` при этом не двигается —
он и есть запись. Части получают `--goal <слаг родителя>`, а закрывать родителя
нечем и незачем: он не выкинут, он стал целью.
**Промежуточного зонтика между целью и задачей нет.** Тип `epic` упразднён: **Зонтика над частями нет никакого.** Тип `epic` упразднён, цель, игравшая его
роль зонтика играет цель, а слишком крупный шаг дробится на шаги помельче под роль после него, — тоже. Если частям нужен общий заголовок, у них общее место в
той же целью. Если частям нужен общий заголовок — значит у них общая списке: они встают подряд, и соседство и есть тот ответ, ради которого заводили
возможность, и её надо назвать целью, а не заводить временный тип. зонтик.
## Когда декомпозиция случается посреди работы ## Когда декомпозиция случается посреди работы
Задача, которая **оказалась крупнее задачи**, распознаётся до того, как под неё Задача, которая **оказалась крупнее задачи**, распознаётся до того, как под неё
заведено предложение об изменении: иначе его придётся выбрасывать. Она выходит заведено предложение об изменении: иначе его придётся выбрасывать. Она выходит
из работы на декомпозицию, а её строка возвращается в беклог с причиной из работы на декомпозицию, а её строка возвращается в беклог с причиной
(`move … --reason "крупнее задачи"`). Части заводятся сразу под той же целью, и (`move … --reason "крупнее задачи"`). **Место в списке частям назначает
**место в очереди им назначает человек**: машина поставит их в конец секции, а человек**: машина поставит их в конец секции, а на стройке место наследуется от
крупная задача редко распадается на что-то менее срочное, чем была сама. родителя (`move --after`), да и на доработке крупная задача редко распадается на
что-то менее срочное, чем была сама.
## Мозговой штурм сырья ## Мозговой штурм сырья
@@ -96,9 +93,9 @@ Applicative-штурм («перечисли задачи, следующие и
applicative. applicative.
2. **Вынеси формы пользователю** через `AskUserQuestion` с компромиссами. Рамку 2. **Вынеси формы пользователю** через `AskUserQuestion` с компромиссами. Рамку
выбирает он: это продуктовое решение, не механика. выбирает он: это продуктовое решение, не механика.
3. **Назови цель.** Выбранная форма служит цели — существующей или новой. Идея, 3. **Назови пользу.** Выбранная форма отвечает на «что станет наблюдаемо иначе».
для которой цель не находится, скорее всего уезжает в `REJECTED.md`, а не Идея, для которой такого ответа не находится, скорее всего уезжает в
заводится задачей. `REJECTED.md`, а не заводится задачей.
4. **Только выбранную форму** дроби по тесту декомпозиции выше и проставь 4. **Только выбранную форму** дроби по тесту декомпозиции выше и проставь
критерии приёмки: без них наследники останутся идеями под другим именем. критерии приёмки: без них наследники останутся идеями под другим именем.
@@ -109,8 +106,8 @@ Applicative-штурм («перечисли задачи, следующие и
## Доклад ## Доклад
- Идея/задача на входе, выбранная рамка (для штурма), задачи-наследники со - Идея/задача на входе, выбранная рамка (для штурма), задачи-наследники со
слагами, целями и секциями. слагами, секциями и местом в списке.
- Судьба родителя: удалён / стал целью / выкинут с причиной. - Судьба родителя: удалён / выкинут с причиной.
- `tasks.py check` после правок. - `tasks.py check` после правок.
- Границы покрытия: какие постановки рассмотрены и какие сознательно отброшены — - Границы покрытия: какие постановки рассмотрены и какие сознательно отброшены —
чтобы штурм не пришлось повторять с нуля. чтобы штурм не пришлось повторять с нуля.
@@ -14,7 +14,6 @@
| Обязательные разделы | `Затрагивает`, `Критерии приёмки` | | Обязательные разделы | `Затрагивает`, `Критерии приёмки` |
| Допустимые сверх того | `Рамки`, `Вопросы` | | Допустимые сверх того | `Рамки`, `Вопросы` |
| Поле места | **Категория** — полка домена беклога | | Поле места | **Категория** — полка домена беклога |
| Цель (`goal:<слаг>`) | нет: цель — это возможность, а здесь её не появляется |
| Индекс | `BACKLOG.md` | | Индекс | `BACKLOG.md` |
| Берётся в работу | да | | Берётся в работу | да |
@@ -43,7 +42,7 @@
## Алгоритм ## Алгоритм
1. **Проверить, что поведение не меняется.** Меняется — это `feature` или `fix`, 1. **Проверить, что поведение не меняется.** Меняется — это `feature` или `fix`,
и у неё другие требования (цель, воспроизведение). и у последнего другие требования (воспроизведение).
2. **Назвать, что перестанет мешать** — одной фразой, адресуясь разработчику. 2. **Назвать, что перестанет мешать** — одной фразой, адресуясь разработчику.
«Прибраться в модуле X» — не ответ: непонятно, что изменится. «Прибраться в модуле X» — не ответ: непонятно, что изменится.
3. **Назвать границы** в `Затрагивает`. У обслуживания они часто не в коде: 3. **Назвать границы** в `Затрагивает`. У обслуживания они часто не в коде:
@@ -55,9 +54,6 @@
5. **Проверить, что это не «заодно».** Обслуживание любит склеиваться в пачку 5. **Проверить, что это не «заодно».** Обслуживание любит склеиваться в пачку
(«обновить зависимости и переписать сборку и убрать мёртвый код»). Не («обновить зависимости и переписать сборку и убрать мёртвый код»). Не
мерджится порознь — это несколько задач ([split.md](split.md)). мерджится порознь — это несколько задач ([split.md](split.md)).
6. **Цель не проставлять.** `chore` служит работоспособности, а не направлению.
Работа по сопровождению проекта при этом видна в роадмапе — секцией
`Сопровождение`, но целью не становится.
## Кто такую задачу решает ## Кто такую задачу решает
@@ -15,18 +15,13 @@
| Обязательные разделы | `Затрагивает`, `Критерии приёмки` | | Обязательные разделы | `Затрагивает`, `Критерии приёмки` |
| Допустимые сверх того | `Рамки`, `Вопросы` | | Допустимые сверх того | `Рамки`, `Вопросы` |
| Поле места | **Категория** — полка домена беклога | | Поле места | **Категория** — полка домена беклога |
| Цель (`goal:<слаг>`) | **обязательна** |
| Индекс | `BACKLOG.md` | | Индекс | `BACKLOG.md` |
| Берётся в работу | да | | Берётся в работу | да |
**Цель обязательна, и это единственный тип, у которого так.** Новая возможность
и есть содержание цели: подходящей нет — либо она заводится, либо перед тобой не
`feature`. `ready` без цели откажет.
## Алгоритм ## Алгоритм
1. **Найти цель или завести её.** Задача без цели, названная функцией, — самый 1. **Проверить, что возможность и правда новая.** Поведение расходится с уже
частый способ пронести в беклог работу, которой никто не заказывал. заявленным — это `fix`, а не `feature`, и требования у него другие.
2. **Назвать границы** в разделе `Затрагивает`: эндпоинт или команда, таблица и 2. **Назвать границы** в разделе `Затрагивает`: эндпоинт или команда, таблица и
миграция, формат на диске, публичный тип пакета, внешний сервис. Названы миграция, формат на диске, публичный тип пакета, внешний сервис. Названы
**границы, а не замысел**: «переписать хранилище на новый драйвер» — замысел, **границы, а не замысел**: «переписать хранилище на новый драйвер» — замысел,
@@ -35,13 +30,12 @@
3. **Написать критерии приёмки** — 2–5 проверяемых утверждений списком, у 3. **Написать критерии приёмки** — 2–5 проверяемых утверждений списком, у
каждого назван оракул. Не «работает корректно», а «повторный прогон даёт тот каждого назван оракул. Не «работает корректно», а «повторный прогон даёт тот
же отпечаток — оракул: команда сверки». же отпечаток — оракул: команда сверки».
4. **Сказать, какую строку «Завершения» цели задача двигает.** Одной строкой в 4. **Поставить её на место в списке.** На стройке место называет зависимость:
теле. Это защита от задачи «отрефакторить X»: она проваливается не потому, `move <слаг> --after <шаг, без которого нельзя>`. На доработке место в
что невидима снаружи, а потому, что не находит строки, к которой относится. очереди назначает груминг, и конец списка законен.
5. **Проверить, что задача одна.** Отвечается всё, но задача не делается одним 5. **Проверить, что задача одна.** Отвечается всё, но задача не делается одним
заходом и не мерджится целиком — это несколько задач под одной целью, дроби заходом и не мерджится целиком — это несколько задач, дроби сразу
сразу ([split.md](split.md)). Промежуточного зонтика между целью и задачей ([split.md](split.md)) и ставь их в списке подряд.
нет.
6. **Реализация** — дело конвейера проекта, не этого скилла. Закрывается 6. **Реализация** — дело конвейера проекта, не этого скилла. Закрывается
`close <слаг> --implemented`: файл и строка удаляются, суть переезжает в `close <слаг> --implemented`: файл и строка удаляются, суть переезжает в
`openspec/specs/` и документацию. `openspec/specs/` и документацию.
@@ -49,8 +43,8 @@
## Что видит машина, а что человек ## Что видит машина, а что человек
Схему типа судит `ready` на входе в работу: **наличие непустого** раздела Схему типа судит `ready` на входе в работу: **наличие непустого** раздела
`Затрагивает`, **число** критериев (меньше двух — отказ, больше пяти — `Затрагивает` и **число** критериев (меньше двух — отказ, больше пяти —
замечание) и цель. Наличие оракула проверяется **эвристикой** — словом «оракул» замечание). Наличие оракула проверяется **эвристикой** — словом «оракул»
в пункте. `check` этого поимённо не говорит, а считает строкой здоровья в пункте. `check` этого поимённо не говорит, а считает строкой здоровья
(`SKILL.md`, «Что механизировано, а что нет»). (`SKILL.md`, «Что механизировано, а что нет»).
@@ -16,7 +16,6 @@
| Обязательные разделы | **`Воспроизведение`**, `Затрагивает`, `Критерии приёмки` | | Обязательные разделы | **`Воспроизведение`**, `Затрагивает`, `Критерии приёмки` |
| Допустимые сверх того | `Рамки`, `Вопросы` | | Допустимые сверх того | `Рамки`, `Вопросы` |
| Поле места | **Категория** — полка домена беклога | | Поле места | **Категория** — полка домена беклога |
| Цель (`goal:<слаг>`) | необязательна |
| Индекс | `BACKLOG.md` | | Индекс | `BACKLOG.md` |
| Берётся в работу | да | | Берётся в работу | да |
@@ -54,9 +53,7 @@
почти всегда есть парный критерий: **прежнее поведение не сломалось** почти всегда есть парный критерий: **прежнее поведение не сломалось**
(«ввод `а1` принимается по-прежнему»). Без него починка чинит одно и ломает («ввод `а1` принимается по-прежнему»). Без него починка чинит одно и ломает
соседнее. соседнее.
6. **Цель не выдумывать.** `fix` служит работоспособности, а не направлению. 6. **Записать дефект в журнал** `docs/review.md` с пометкой «проскочил / пойман
Придуманная цель — то же враньё, от которого спасает тип.
7. **Записать дефект в журнал** `docs/review.md` с пометкой «проскочил / пойман
ревью». Проскочившие — проверочный набор для калибровки конвейера; пойманные с ревью». Проскочившие — проверочный набор для калибровки конвейера; пойманные с
оракулом — лучшая опора для прохода ревью: проектные, воспроизводимые, оракулом — лучшая опора для прохода ревью: проектные, воспроизводимые,
однажды оказавшиеся правдой. однажды оказавшиеся правдой.
@@ -1,4 +1,4 @@
# Формат записей и индексов # Формат записей и индекса
Заголовок, мета-блок и строку индекса ставит `tasks.py add` — руками их не Заголовок, мета-блок и строку индекса ставит `tasks.py add` — руками их не
пишут. Этот файл описывает **общую форму** любой записи и то, что проверяет пишут. Этот файл описывает **общую форму** любой записи и то, что проверяет
@@ -9,7 +9,6 @@
| Тип | Файл | Одной строкой | | Тип | Файл | Одной строкой |
| --- | --- | --- | | --- | --- | --- |
| 🎯 `goal` | [task-goal.md](task-goal.md) | возможность приложения |
| ✨ `feature` | [task-feature.md](task-feature.md) | снаружи появляется то, чего не было | | ✨ `feature` | [task-feature.md](task-feature.md) | снаружи появляется то, чего не было |
| 🐞 `fix` | [task-fix.md](task-fix.md) | поведение расходится с заявленным | | 🐞 `fix` | [task-fix.md](task-fix.md) | поведение расходится с заявленным |
| 🧹 `chore` | [task-chore.md](task-chore.md) | обслуживание, поведение не меняется | | 🧹 `chore` | [task-chore.md](task-chore.md) | обслуживание, поведение не меняется |
@@ -25,7 +24,6 @@
- **Тип:** fix - **Тип:** fix
- **Категория:** Ядро — вернулась из работы: остаток писал нерешённое в журнал - **Категория:** Ядро — вернулась из работы: остаток писал нерешённое в журнал
- **Зачем:** ввод «а1б2» ходит в a1 — игрок не видит, что ошибся, и винит игру - **Зачем:** ввод «а1б2» ходит в a1 — игрок не видит, что ошибся, и винит игру
- **Теги:** goal:merge-robustness
Разбор хода читает первые два символа и молча выбрасывает остаток строки. Разбор хода читает первые два символа и молча выбрасывает остаток строки.
@@ -55,8 +53,8 @@
**эмодзи типа**, и она **производна**: её ставит `add` и чинит `check --fix` **эмодзи типа**, и она **производна**: её ставит `add` и чинит `check --fix`
по полю меты. Второго дома у типа нет — эмодзи это его отображение, как по полю меты. Второго дома у типа нет — эмодзи это его отображение, как
строка индекса это отображение файла. строка индекса это отображение файла.
- **Форма заголовка — по типу.** Цель отвечает на «что приложение будет уметь»; - **Форма заголовка — по типу.** `feature`, `fix` и `chore` отвечают на «что
`feature`, `fix` и `chore` — на «что нужно сделать», глаголом в неопределённой нужно сделать», глаголом в неопределённой
форме, перед ним допускается «не»; `research` называет предмет разведки и форме, перед ним допускается «не»; `research` называет предмет разведки и
формы действия **не несёт намеренно**. Почему так — SKILL.md, «Как написана формы действия **не несёт намеренно**. Почему так — SKILL.md, «Как написана
задача». `check` считает заголовки не в форме действия и печатает число в задача». `check` считает заголовки не в форме действия и печатает число в
@@ -67,9 +65,8 @@
теги необязательны. Нераспознанные поля сохраняются: скрипт правит свои и не теги необязательны. Нераспознанные поля сохраняются: скрипт правит свои и не
трогает чужие. трогает чужие.
- **Тип — первым полем.** Он решает, что у записи вообще может быть: какие - **Тип — первым полем.** Он решает, что у записи вообще может быть: какие
разделы обязательны, нужна ли цель, берётся ли она в работу, — и читается разделы обязательны и берётся ли она в работу, — и читается раньше всего
раньше всего остального. Словарь **закрыт**: `goal` | `feature` | `fix` | остального. Словарь **закрыт**: `feature` | `fix` | `chore` | `research`. Не подходит ни один — это сигнал, что в записи их два и
`chore` | `research`. Не подходит ни один — это сигнал, что в записи их два и
её надо разделить. её надо разделить.
- **«Зачем» отвечает на «зачем нужна эта задача»** — состояние, остаток, боль. - **«Зачем» отвечает на «зачем нужна эта задача»** — состояние, остаток, боль.
Не пересказ задачи: пересказ уже есть по ссылке. Живёт здесь, а не только в Не пересказ задачи: пересказ уже есть по ссылке. Живёт здесь, а не только в
@@ -86,19 +83,16 @@
Тело — не план реализации и не спецификация: принятое и реализованное переезжает Тело — не план реализации и не спецификация: принятое и реализованное переезжает
в документацию проекта, а файл задачи удаляется. в документацию проекта, а файл задачи удаляется.
### Поле места: «Категория» и «Секция» ### Поле места: «Категория»
Поле называет, **где числится строка**, и имя у него **зависит от типа**: Поле называет **секцию беклога, в которой числится строка** — полку домена
(`Ядро`, `Инфра`, …), куда задачу положили и куда вернут, если она уйдёт в работу
и вернётся. **На стройке секция одна**, и поле называет её же: различать ей
нечего, но производность от заголовка индекса сохраняется и там.
| Тип | Поле | Значения | Что это | Прежнее имя поля — **«Секция»**: так оно называлось у целей, указывая на часть
| --- | --- | --- | --- | роадмапа. Разбор его по-прежнему принимает, `check` называет дрейфом, `check
| `goal` | **Секция** | `Запланировано`, `Направления`, `Сопровождение` | часть роадмапа: состояние очереди | --fix` переименовывает.
| прочие | **Категория** | секции беклога проекта (`Ядро`, `Инфра`, …) | полка домена, на которой задача лежит |
Разные имена потому, что это **разные вещи**. У задачи это полка: куда её
положили и куда вернут, если она уйдёт в работу и вернётся. У цели оно называет
не полку, а место в очереди работ. Одно имя на два смысла их и смешивало; `check` называет
несовпадение дрейфом, `check --fix` переименовывает.
Имя самого места принадлежит **заголовку индекса** — файл на него лишь Имя самого места принадлежит **заголовку индекса** — файл на него лишь
ссылается, и принадлежность сверяется по нижнему регистру. ссылается, и принадлежность сверяется по нижнему регистру.
@@ -111,7 +105,8 @@
| --- | --- | | --- | --- |
| префикс `[goal]` / `[idea]` в H1 | поле **Тип** + эмодзи в H1; `[idea]``research` | | префикс `[goal]` / `[idea]` в H1 | поле **Тип** + эмодзи в H1; `[idea]``research` |
| тег `kind:<род>` | поле **Тип** (род работы стал типом) | | тег `kind:<род>` | поле **Тип** (род работы стал типом) |
| поле **Секция** у задачи | поле **Категория** | | поле **Секция** | поле **Категория** |
| теги `goal:<слаг>` и `decomposed` | сняты: целей больше нет |
| поле **Хук** | поле **Зачем** | | поле **Хук** | поле **Зачем** |
| мета одной строкой через `·` | мета списком, поле на строку | | мета одной строкой через `·` | мета списком, поле на строку |
@@ -147,8 +142,8 @@
забыли назвать, она от отсутствующей не отличает. Раздела нет — отказ во взятии: забыли назвать, она от отсутствующей не отличает. Раздела нет — отказ во взятии:
оценивать нечем. оценивать нечем.
**У `goal` и `research` раздела нет** — у первой границы называют её задачи, у **У `research` раздела нет** — её границы становятся известны, когда из разведки
второй они становятся известны, когда из разведки родятся задачи. родятся задачи.
### Критерии приёмки ### Критерии приёмки
@@ -166,8 +161,7 @@
что проверено больше проверенного, хуже, чем не проверять вовсе. что проверено больше проверенного, хуже, чем не проверять вовсе.
**У `research` критериев нет** — её приёмка это записанный ответ, и описывается **У `research` критериев нет** — её приёмка это записанный ответ, и описывается
она разделами «Вопрос» и «Куда ляжет ответ». **У `goal` их заменяет она разделами «Вопрос» и «Куда ляжет ответ».
«Завершение».**
**Критерии — пол, но расхождение с ними есть дефект критериев.** Если приёмщик **Критерии — пол, но расхождение с ними есть дефект критериев.** Если приёмщик
видит, что критерии закрыты, а суть задачи не достигнута, он **правит критерии и видит, что критерии закрыты, а суть задачи не достигнута, он **правит критерии и
@@ -210,44 +204,6 @@
на снятый тег при непустом разделе посоветует тег вернуть. Снять тег, не на снятый тег при непустом разделе посоветует тег вернуть. Снять тег, не
опустошив раздел, — значит закольцевать себя между двумя советами. опустошив раздел, — значит закольцевать себя между двумя советами.
## Файл цели
Форма та же, разделы и алгоритм — [task-goal.md](task-goal.md).
```markdown
# 🎯 Исход слияния не зависит от порядка доставки
- **Тип:** goal
- **Секция:** Направления
- **Теги:** decomposed
Ради чего: точки из разных доставок сходятся в один часовой объект, и сегодня
исход столкновения зависит от порядка доставки, а не от содержания.
## Завершение
- повторная доставка тех же точек в другом порядке даёт то же состояние;
- накопительная метрика за сутки не уменьшается после повторной доставки;
- в логе видно, какая из двух точек выиграла и почему.
```
- **Задачи цели здесь не перечисляются.** Перечень даёт
`tasks.py list --goal <слаг>`; хранимый список стал бы третьим индексом и
поехал бы на первой же закрытой задаче.
- **Тег `decomposed`** отличает «цель ещё не разобрана» от «все её задачи
закрыты» — два состояния, у которых снаружи один и тот же признак: задач нет.
Пометка именно **тегом**, а не строкой в теле: только так она проверяется.
`check` напоминает о нём у цели без задач замечанием — неразобранная цель
законна и зелёного прогона не ломает; `check --fix` сам ставит его цели, у
которой задачи есть, а цель с тегом и без задач — прямое приглашение закрыть.
- Цель живёт в `ROADMAP.md` и **никогда** — в `BACKLOG.md`.
- **Достигнутая цель не исчезает.** `close <слаг> --implemented` удаляет файл и
переносит строку в секцию `Готово` с датой:
`- 2026-08-04 \`merge-order\` — Исход слияния не зависит от порядка доставки. …`
Ссылки на файл в ней нет — файл удалён, а битая ссылка это ошибка `check`.
Поведение живёт в спеках проекта; роадмап отвечает, **когда и в каком порядке**
оно появилось.
## Слаг ## Слаг
Латиница и цифры, kebab-case, без ведущих, хвостовых и двойных дефисов Латиница и цифры, kebab-case, без ведущих, хвостовых и двойных дефисов
@@ -261,9 +217,9 @@
проходом по всем местам, где слаг упомянут, иначе останутся битые ссылки, проходом по всем местам, где слаг упомянут, иначе останутся битые ссылки,
которых никто не проверяет. которых никто не проверяет.
## Индексы ## Индекс
Строка везде одной формы: Строка одной формы:
```markdown ```markdown
- [🐞 Заголовок дословно](items/slug.md) — зачем - [🐞 Заголовок дословно](items/slug.md) — зачем
@@ -276,52 +232,47 @@
| Файл | Что отвечает | Секции | | Файл | Что отвечает | Секции |
| --- | --- | --- | | --- | --- | --- |
| `ROADMAP.md` | что приложение уже умеет и чего ещё не умеет | канонические и в этом порядке: `Запланировано`, `Направления`, `Сопровождение`, `Готово` (англ. `Planned`, `Directions`, `Operations`, `Done`) | | `BACKLOG.md` | что **можно взять**, в значимом порядке | называет проект; на стройке ровно одна (умолчание `План`), на доработке сколько нужно (умолчание `Ядро`/`Инфра`) |
| `BACKLOG.md` | что **можно взять** — только задачи, **в порядке очереди** | категории проекта (по умолчанию Ядро/Инфра) |
| `REJECTED.md` | что ушло без реализации и почему | — | | `REJECTED.md` | что ушло без реализации и почему | — |
Секции — **единственные заголовки `##` в индексе**: любой другой `##` в Секции — **единственные заголовки `##` в индексе**: любой другой `##` в
преамбуле проверка сочтёт секцией. преамбуле проверка сочтёт секцией.
**Порядок строк внутри секции беклога значим: это очередь.** Первая строка — то, **Порядок строк внутри секции значим, и стадия решает, что он значит:** на
что делают следующим; назначает порядок человек на груминге, и двигают его стройке зависимость, на доработке важность (SKILL.md, «Две стадии»). Назначает
`move --after` и `move --first`. Одно место из очереди изъято и **производно от его человек — раскладывая шаги или на груминге, — и двигают его `move --after`
типа и заполненности**: **сырьё** (`research` без раздела «Вопрос») стоит в конце и `move --first`. Одно место из очереди изъято и **производно от типа и
своей секции, потому что его не берут, и между берущимся оно каждый раз требует заполненности**: **сырьё** (`research` без раздела «Вопрос») стоит в конце своей
секции, потому что его не берут, и между берущимся оно каждый раз требует
открыть файл, чтобы это понять. Проверяет `check`, переставляет `check --fix`, открыть файл, чтобы это понять. Проверяет `check`, переставляет `check --fix`,
и человек этот порядок не назначает — иначе он был бы приоритетом, которого и человек этот порядок не назначает — иначе он был бы решением, которого здесь
здесь нет. нет.
**Секции «блокеры» среди них нет.** Блокер — состояние, а не полка: он живёт до **Секции «блокеры» среди них нет.** Блокер — состояние, а не полка: он живёт до
ответа человека, а следы остаются вопросами в файлах задач. ответа человека, а следы остаются вопросами в файлах задач.
Постоянно пустая секция со старой семантикой «разбираются пачками» противоречила Постоянно пустая секция со старой семантикой «разбираются пачками» противоречила
бы правилу «эскалируем немедленно», поэтому `init` её не заводит, а `check` бы правилу «эскалируем немедленно», поэтому `init` её не заводит, а `check`
говорит о ней в чужом беклоге. Переезжаешь с такой секцией — удали её. В секции говорит о ней в чужом беклоге. Переезжаешь с такой секцией — удали её.
**`Запланировано`** очередь значима и обосновывается прозой; двигают строку
`move <slug> --section Запланировано --after <другой>`. В секции **`Готово`**
строки не той формы, что у прочих индексов: дата, слаг, заголовок — как в
`REJECTED.md`, и по той же причине (файла уже нет, ссылаться некуда).
**Секции роадмапа закреплены** — состав, полнота, единство языка и **порядок** **Имена секций проект выбирает сам, а количество ограничено стадией:** на
проверяются `check`; категории беклога проект называет сам. Почему так — стройке секция одна, потому что порядок там зависимость, и разложенный по полкам
SKILL.md. Порядок закреплён потому, что `Готово` копится: стоя первым, список перестаёт быть планом. Проверяет `check`; слить секции сам он не берётся —
достигнутое отодвигает за экран то, ради чего роадмап открывают чаще всего. в каком порядке пойдут строки слитых полок, знает только человек.
**Заголовок секции пишется с прописной и отбивается пустой строкой с обеих **Заголовок секции пишется с прописной и отбивается пустой строкой с обеих
сторон** — во всех индексах, включая категории беклога, имена которых выбирает сторон.** Отбивку правит `check --fix`; он же сводит написание места в мете файла
проект. Написание канонических секций и отбивку правит `check --fix`; он же с заголовком индекса.
сводит написание места в мете файла с заголовком индекса.
Индексы **производны**: расходятся с файлом — правим индексы (`check --fix`). Индекс **производен**: расходится с файлом — правим индекс (`check --fix`).
Строку руками не пишут. Строку руками не пишут.
Отсюда же ответ на «а если оборвётся посередине». Мутация сперва проверяет всё Отсюда же ответ на «а если оборвётся посередине». Мутация сперва проверяет всё
и складывает правки, и только потом пишет: сначала все временные файлы, потом и складывает правки, и только потом пишет: сначала все временные файлы, потом
переименования подряд. Полной транзакции на несколько файлов файловая система не переименования подряд. Полной транзакции на несколько файлов файловая система не
даёт, но окно сжато до цепочки переименований, а **всё, что в нём может даёт, но окно сжато до цепочки переименований, а **всё, что в нём может
разъехаться, — производное**: файлы целы, индексы восстанавливает `check --fix`. разъехаться, — производное**: файлы целы, индекс восстанавливает `check --fix`.
Поэтому отказ на второй задаче из пяти не оставляет первую переписанной при Поэтому отказ на второй задаче из пяти не оставляет первую переписанной при
нетронутых индексах. нетронутом индексе.
## `REJECTED.md` ## `REJECTED.md`
@@ -346,27 +297,24 @@ SKILL.md. Порядок закреплён потому, что `Готово`
Разметка сверх типа. Тип полем, потому что он один и обязателен; теги — потому Разметка сверх типа. Тип полем, потому что он один и обязателен; теги — потому
что их много и `list --tag` уже умеет отбирать по ним порцию разбора. что их много и `list --tag` уже умеет отбирать по ним порцию разбора.
- `goal:<слаг>` — цель, которой служит задача. Обязателен **у `feature`**:
новая возможность и есть содержание цели. У `fix`, `chore` и `research` его
может не быть — они служат работоспособности, а не направлению.
- `question` — в файле есть неразобранный раздел «Вопросы». - `question` — в файле есть неразобранный раздел «Вопросы».
- `decomposed` — на цели: разложена на задачи (см. «Файл цели»).
Тега `kind:<род>` больше нет: род работы стал типом. Оставшийся в файле `check` Тегов `kind:<род>`, `goal:<слаг>` и `decomposed` больше нет: род работы стал
называет дрейфом, а `check --fix` снимает, перенеся значение в поле «Тип». типом, а цели упразднены. Оставшиеся в файле `check` называет дрейфом, а `check
--fix` снимает (значение `kind:` при этом переезжает в поле «Тип»).
Отбор — `list --tag a,b`: перечисленные через запятую теги требуются **все Отбор — `list --tag a,b`: перечисленные через запятую теги требуются **все
сразу** (это И, не ИЛИ). Тег, которого нет ни у одной задачи, `list` называет сразу** (это И, не ИЛИ). Тег, которого нет ни у одной задачи, `list` называет
вслух: молчаливый ноль читается как «таких задач нет», а чаще это опечатка. вслух: молчаливый ноль читается как «таких задач нет», а чаще это опечатка.
Свои теги проект заводит свободно (партия ревью `review-ГГГГ-ММ-ДД`, тема, Свои теги проект заводит свободно (партия ревью `review-ГГГГ-ММ-ДД`, тема,
источник) — словарь не фиксирован. В индексы теги не выносим: индексы источник) — словарь не фиксирован. В индекс теги не выносим: он
производны, отбор делает `list --tag`, а не глаза. производен, отбор делает `list --tag`, а не глаза.
## Тест «готова к взятию» ## Тест «готова к взятию»
Задача готова, если из файла отвечаются четыре вопроса. Первый и четвёртый Задача готова, если из файла отвечаются три вопроса. Первый общий, второй и
общие, второй и третий у каждого типа свои и перечислены в его файле. третий у каждого типа свои и перечислены в его файле.
1. **Что станет наблюдаемо иначе**, когда она сделана — снаружи: пользователю, 1. **Что станет наблюдаемо иначе**, когда она сделана — снаружи: пользователю,
владельцу сервиса или разработчику. «Отрефакторить X» — не ответ; «перестанет владельцу сервиса или разработчику. «Отрефакторить X» — не ответ; «перестанет
@@ -379,26 +327,13 @@ SKILL.md. Порядок закреплён потому, что `Готово`
`chore``Затрагивает`. `chore``Затрагивает`.
3. **По чему видно, что закончено** — критерии приёмки с оракулами; 3. **По чему видно, что закончено** — критерии приёмки с оракулами;
у `research` вместо них `Куда ляжет ответ`. у `research` вместо них `Куда ляжет ответ`.
4. **Какую часть «Завершения» своей цели она двигает** — у задачи с целью. Не отвечается любой из трёх → это ещё не задача, а **сырьё**: тип `research` без
Строкой: «двигает пункт 2 «Завершения» — накопительная метрика перестаёт раздела «Вопрос», место — конец секции, работа над ним — штурм.
уменьшаться». Это и есть защита от задачи «отрефакторить X»: она проваливает
тест не потому, что невидима снаружи, а потому, что не находит строки, к
которой относится. Заодно видно обратное — достаточен ли набор задач для
цели: строка «Завершения», к которой не относится ни одна задача, это
незакрытая часть возможности.
**У задачи без цели** (`fix`, `chore`, `research`) вопрос не задаётся: они
служат работоспособности, а не направлению.
Не отвечается первый, второй или третий вопрос → это ещё не задача, а **сырьё**:
тип `research` без раздела «Вопрос», место — конец секции, работа над ним —
штурм. Не отвечается четвёртый у `feature` → либо цель есть и не проставлена,
либо это не новая возможность.
Отвечается всё, но задача не делается одним заходом и не мерджится целиком → Отвечается всё, но задача не делается одним заходом и не мерджится целиком →
это **несколько задач под одной целью**, дроби сразу. Промежуточного зонтика это **несколько задач**, дроби сразу и ставь их в списке подряд. Промежуточного
между целью и задачей нет: тип `[epic]` упразднён, потому что зонтиком стала зонтика между планом и задачей нет: тип `[epic]` упразднён, и цель, ставшая
сама цель. зонтиком после него, упразднена тоже.
Тест применяется при заведении и при переоценке. К старым задачам, которых Тест применяется при заведении и при переоценке. К старым задачам, которых
операция не касается, задним числом не применяется — беклог не переоформляют операция не касается, задним числом не применяется — беклог не переоформляют
@@ -1,93 +0,0 @@
# 🎯 `goal` — возможность приложения
Цель отвечает на **«что приложение будет уметь»**. Не область работ и не имя
подсистемы: не «Работа со слиянием», а «Исход слияния не зависит от порядка
доставки». Свойство поведения — тоже возможность.
Общая форма записи (мета, слаг, строка индекса) — [task-format.md](task-format.md).
Здесь только то, что у этого типа своё.
## Схема
| | |
| --- | --- |
| Заголовок отвечает на | что приложение будет уметь |
| Обязательные разделы | `Завершение` |
| Допустимые сверх того | — |
| Поле места | **Секция** — часть роадмапа |
| Цель (`goal:<слаг>`) | запрещена: цель и есть цель |
| Индекс | `ROADMAP.md`, и никогда `BACKLOG.md` |
| Берётся в работу | нет — берутся её задачи |
Поле места у цели называется **«Секция»**, а не «Категория», и это не разнобой:
у задачи оно называет полку домена, на которой она лежит, а у цели
— часть роадмапа, то есть состояние очереди. Одно имя на два смысла их и
смешивало.
## «Завершение» — списком, а не абзацем
Это признаки того, что приложение **уже умеет**, и на строки этого раздела
ссылаются задачи цели: «двигает пункт 2 «Завершения» — накопительная метрика
перестаёт уменьшаться». Абзацем такая ссылка не берётся, поэтому список.
Отсюда же читается обратное и более полезное: **строка «Завершения», к которой
не относится ни одна задача, — незакрытая часть возможности**. Достаточность
набора задач видна из самой цели, а не из чьей-то памяти.
## Алгоритм
1. **Проверить, что это возможность, а не работа.** Работа, которой держат
проект, на вопрос «что приложение будет уметь» не отвечает; состав перечислен
[в словаре сопровождения](../../../shared/operations.md). Ей отведена секция
`Сопровождение` — там она видна в том же
экране и не читается как обещание продукта. Граница проходит по тому,
**кто наблюдает**:
«приложение сообщает о своём состоянии» — возможность, «дежурный видит
состояние на одном экране» — сопровождение.
2. **Выбрать секцию.** Очередь значима и обоснована прозой — `Запланировано`;
тянется долго и очереди не имеет — `Направления`; про то, чем держат проект,
`Сопровождение`. В `Готово` кладёт сам `close`.
3. **Написать «Завершение»** — 2–5 наблюдаемых признаков списком. Пишутся до
декомпозиции: иначе задачи придумают себе цель задним числом.
4. **Разложить на задачи** и проставить им `goal:<слаг>`. Перечень задач в теле
цели **не хранится** — он был бы третьим индексом и поехал бы на первой же
закрытой задаче; выводит `tasks.py list --goal <слаг>`.
5. **Пометить `decomposed`.** Тег отличает «ещё не разобрана» от «все задачи
закрыты» — два состояния с одним внешним признаком. `check --fix` ставит его
сам цели, у которой задачи есть.
6. **Закрыть достигнутой**`close <слаг> --implemented`, когда не осталось
открытых задач. Файл удаляется, строка с датой переезжает в `Готово`. Скрипт
откажет, если задачи ещё живы.
## Отменённая цель — сперва задачи, потом цель
Замысел бывает неверен, и цель отменяют, не достигнув. Порядок обратный
завершению и держится тем же запретом: цель, закрытая поверх живых задач,
оставила бы их сиротами, и `close` этого не даст.
1. **Разобрать её задачи поштучно.** Задача, теряющая смысл вместе с целью, —
`close <slug> --reason "<почему>"`; задача, переживающая цель, — `edit <slug>
--goal <другая>`. **Причина обязательна и пишется своя каждой:** «цель
отменена» это не причина, а пересказ команды, и в `REJECTED.md` от него нет
пользы через квартал.
2. **Закрыть саму цель**`close <слаг> --reason "<почему замысел отменён>"`.
Файл удаляется, строка с причиной и датой уходит в `REJECTED.md`. В `Готово`
не попадает: `Готово` отвечает «что приложение умеет», а отменённая цель не
умеет ничего.
**Место этому — груминг, а не отдельный заход.** Отмена цели значит
разбор всех её задач, а разбор задач и есть шаг 3 груминга
(скилл `task-groom`, «что перестало быть важным»). Отменять на ходу,
между делом, — верный способ закрыть скопом то, что стоило перевесить.
## Что видит машина, а что человек
`check` считает цели, различает разобранные и пустые, ставит `decomposed`,
запрещает закрыть цель с живыми задачами и держит `Секцию` в согласии с
заголовком роадмапа. **Годность формулировки — не машине**: «возможность это или
область работ» решает [агент вычитки](../SKILL.md#вычитка-два-прохода-а-не-один).
Достигнутая цель **не исчезает**: «что приложение умеет» — половина вопроса, ради
которого роадмап открывают. Вторым домом поведения роадмап при этом не
становится: нормативное поведение живёт в `openspec/specs/`, роадмап отвечает,
**когда и в каком порядке** оно появилось.
@@ -14,7 +14,6 @@
| Обязательные разделы | `Вопрос`, `Куда ляжет ответ` | | Обязательные разделы | `Вопрос`, `Куда ляжет ответ` |
| Допустимые сверх того | `Рамки`, `Вопросы` | | Допустимые сверх того | `Рамки`, `Вопросы` |
| Поле места | **Категория** — полка домена беклога | | Поле места | **Категория** — полка домена беклога |
| Цель (`goal:<слаг>`) | нет |
| Индекс | `BACKLOG.md` | | Индекс | `BACKLOG.md` |
| Берётся в работу | да — **но только с заполненным «Вопросом»** | | Берётся в работу | да — **но только с заполненным «Вопросом»** |
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -10,7 +10,7 @@
куда большей вероятностью, чем новая тема. куда большей вероятностью, чем новая тема.
Адрес документа принадлежит одному скиллу, а называют его все: `docs/*` стоит Адрес документа принадлежит одному скиллу, а называют его все: `docs/*` стоит
примерно в сорока местах конвейера, `tasks/ROADMAP.md` в четырёх местах примерно в сорока местах конвейера, `tasks/BACKLOG.md` в нескольких местах
канона. Переименование в каноне до этих мест не доходит. канона. Переименование в каноне до этих мест не доходит.
**Почему тут нужна машина, а не аккуратность.** Прогон ревью умеет честно **Почему тут нужна машина, а не аккуратность.** Прогон ревью умеет честно