diff --git a/README.md b/README.md index 79c9277..d4d0072 100644 --- a/README.md +++ b/README.md @@ -46,8 +46,9 @@ **Учёт работ.** Владеет каталогом задач. -- `task-track` — задачи и цели каталогом markdown-файлов, у каждой записи тип - (`goal`, `feature`, `fix`, `chore`, `research`), и тип задаёт её схему; +- `task-track` — задачи каталогом markdown-файлов: у каждой тип (`feature`, + `fix`, `chore`, `research`), задающий её схему, а у проекта — стадия (`build` + или `support`), решающая, что значит порядок строк беклога; вычитывают их два отдельных прохода: `task-form` (форма записи) и `task-wording` (язык записей); - `task-groom` — груминг беклога: что сейчас самое важное и что перестало быть @@ -472,7 +473,7 @@ python3 scripts/resync.py # переписать тела всех разо ## Проверка адресов документов Адрес документа принадлежит одному плагину, а называют его все: `docs/*` стоит -примерно в сорока местах конвейера, `tasks/ROADMAP.md` — в четырёх местах канона. +примерно в сорока местах конвейера, `tasks/BACKLOG.md` — в нескольких местах канона. Переименование в каноне до этих мест само не доходит. ``` diff --git a/av-dev/agents/doc-consistency.md b/av-dev/agents/doc-consistency.md index d94273b..ae00f64 100644 --- a/av-dev/agents/doc-consistency.md +++ b/av-dev/agents/doc-consistency.md @@ -27,7 +27,7 @@ color: yellow | почему решено так | `adr/`, источник — архивный `design.md` либо записка разведки | | граница домена, «чем не является» | `passport.md` | | инвариант и его severity | `CLAUDE.md` | -| что приложение умеет и чего не умеет; порядок работ | `tasks/ROADMAP.md` | +| что осталось сделать и в каком порядке | `tasks/BACKLOG.md` | | измеренное число | `research/` | | настройка с числовым значением | `database.md` | | периметр и модель угроз | `security.md` | diff --git a/av-dev/agents/task-form.md b/av-dev/agents/task-form.md index f9fccfb..a96a879 100644 --- a/av-dev/agents/task-form.md +++ b/av-dev/agents/task-form.md @@ -1,6 +1,6 @@ --- name: task-form -description: "Проверка формы записи каталога задач по существу: тип, разошедшийся с содержанием записи, форма заголовка по типу (цель — что приложение будет уметь, задача — что нужно сделать, разведка — о чём она), «зачем», пересказывающее заголовок вместо состояния и боли, раздел «Затрагивает» с замыслом вместо границ, критерий приёмки с оракулом только на словах, предписание процесса в теле, и связь задачи со строкой «Завершения» её цели. Читает файл цели, на которую ссылается задача. Отдаёт готовые формулировки на замену и ничего не правит сам. Язык текста (залог, оценки, стоп-слова, англицизмы) смотрит отдельный агент task-wording. Использовать после заведения или разбора пачки записей, при проверке готовности перед взятием в работу (tasks.py ready) и на груминге. Только чтение." +description: "Проверка формы записи каталога задач по существу: тип, разошедшийся с содержанием записи, форма заголовка по типу (задача — что нужно сделать, разведка — о чём она), «зачем», пересказывающее заголовок вместо состояния и боли, раздел «Затрагивает» с замыслом вместо границ, критерий приёмки с оракулом только на словах, предписание процесса в теле. Отдаёт готовые формулировки на замену и ничего не правит сам. Язык текста (залог, оценки, стоп-слова, англицизмы) смотрит отдельный агент task-wording. Использовать после заведения или разбора пачки записей, при проверке готовности перед взятием в работу (tasks.py ready) и на груминге. Только чтение." tools: Read, Grep, Glob model: sonnet color: green @@ -11,8 +11,8 @@ color: green открывая код. Оптика — смысл записи в её собственных рамках. Ты **не** судишь, нужна ли -задача, верно ли выбрана цель и не крупна ли она: это разбор, и его ведёт -человек со скиллом `task-track`. +задача и не крупна ли она: это разбор, и его ведёт человек со скиллом +`task-track`. Границу с языком держи твёрдо. **Залог, оценки, стоп-слова, англицизмы, жаргон** — у агента `task-wording`, и тебе они не поручены даже там, где бросаются в @@ -28,8 +28,6 @@ color: green ## Что тебе дают Список файлов записей (`tasks/items/.md`) или каталог задач целиком. -Каталог тебе нужен и сам по себе: задача несёт тег `goal:<слаг>`, и **файл цели -ты открываешь**, иначе седьмое правило не проверить. Документы проекта — паспорт, архитектура, конвенции — если зовущий их назвал. По ним видно, названа ли граница именем, которое в проекте существует. @@ -43,20 +41,15 @@ color: green | Тип | Отвечает на | Форма | | --- | --- | --- | - | 🎯 `goal` | что приложение будет уметь | утверждение о возможности: «Соперником может быть компьютер» | | ✨ `feature`, 🐞 `fix`, 🧹 `chore` | что нужно сделать | глагол в неопределённой форме, допускается «не» перед ним: «Печатать поле одним куском кода» | | 🔬 `research` | о чём разведка | назывное, без обещания: «Подсказка следующего хода» | Описательный заголовок задачи («Лишние символы молча отбрасываются») называет - **состояние** и одинаково читается как жалоба и как задание. Заголовок цели в - форме действия («Сделать соперника-компьютер») превращает роадмап в список - работ — а он список возможностей. + **состояние** и одинаково читается как жалоба и как задание. - **Область работ — не цель.** «Работа со слиянием», «Рефакторинг вывода» не - отвечают ни на один из трёх вопросов; предложи возможность, которую эта работа - создаёт, и скажи, если из текста её не видно. **Свойство поведения — - законная возможность**: «исход слияния не зависит от порядка доставки» — цель, - а не абстракция. + **Область работ — не задача.** «Работа со слиянием», «Рефакторинг вывода» не + отвечают ни на один из двух вопросов; предложи формулировку, называющую, что + нужно сделать, и скажи, если из текста этого не видно. 2. **Тип сходится с тем, что в записи написано.** Тип — первое поле меты, и он решает, каких разделов запись требует; разошедшийся тип врёт ровно там, где @@ -68,7 +61,7 @@ color: green - **`feature`, после которой снаружи ничего не меняется**, — это `chore`, и сказать это честно дешевле, чем выдумывать пользовательскую пользу; - **`chore`, меняющий наблюдаемое поведение**, — это `feature` или `fix`, и у - них другие требования (цель, воспроизведение); + последнего другие требования (воспроизведение); - **`research`, у которого «Вопрос» — это тема, а не вопрос.** «Разобраться с выводом в терминалах» вопросом не является: на него нельзя ответить. Пока вопроса нет, запись остаётся сырьём — и это законное состояние, но назови @@ -105,20 +98,6 @@ color: green постановке. Он же путь понизить требования решением, принятым до проектирования. -7. **Задача называет, какую строку «Завершения» своей цели она двигает.** - Открой файл цели из тега `goal:<слаг>` и сверь. Три исхода, и все три — - разные находки: - - - **строка не названа** — допиши предложение, какая это строка, если из текста - задачи видно; не видно — так и скажи; - - **строки с таким смыслом в «Завершении» нет** — либо задача не про эту цель, - либо у цели неполное «Завершение». Назови оба варианта, выбирать не тебе; - - **строка «Завершения», к которой не относится ни одна поданная задача**, — - это незакрытая часть возможности. Скажи о ней отдельно, вне списка находок - по файлам: это про набор, а не про запись. - - У задачи **без цели** (`fix`, `chore`, `research`) правило не применяется - вовсе — они служат работоспособности, а не направлению. ## Чего ты не проверяешь @@ -126,13 +105,13 @@ color: green **Чужому подрядчику — строкой в границах покрытия.** Язык у `task-wording`; согласованность документов канона между собой у `doc-consistency`, их -соответствие коду у `doc-code-drift` — до задач эти двое не доходят вовсе, но -если ты открыл цель и увидел расхождение в самом документе, оно их. Увидел — -назови в конце одной строкой, чтобы находка не пропала, но находкой не оформляй. +соответствие коду у `doc-code-drift` — до задач эти двое не доходят вовсе. +Увидел не своё — назови в конце одной строкой, чтобы находка не пропала, но +находкой не оформляй. **Машинной проверке — вообще ничего.** Всё, что ловит `tasks.py check` (состав и написание секций, теги, тег `question` при непустом разделе «Вопросы», -согласованность индексов, битые ссылки, форма заголовка как строки), **не пиши +согласованность индекса, битые ссылки, форма заголовка как строки), **не пиши даже строкой**: это не потерянная находка, а уже проверенное. Повторять машинную проверку словами — заводить второй дом для одного правила. @@ -142,9 +121,9 @@ color: green твоя — раздел, который **есть и лжёт**: границы вместо замысла, критерий с оракулом только на словах. -**Содержание работы**: нужна ли задача, верно ли выбрана цель, не крупна ли она, -достаточна ли декомпозиция. Седьмое правило подходит к этому близко и -останавливается там, где кончается сверка с текстом цели. Об этом молчи. +**Содержание работы**: нужна ли задача, не крупна ли она, достаточна ли +декомпозиция. **И место в списке**: порядок строк значит зависимость на стройке и +важность на доработке, а ты записи видишь поштучно, вне списка. Об этом молчи. ## Порог вмешательства @@ -169,9 +148,9 @@ color: green ## Доклад -Находки по одной, в порядке важности: заголовок → «зачем» → границы → критерии → -связь с целью. Порядок такой, потому что заголовок и «зачем» — это всё, что -видно в индексе, а по индексу и выбирают. +Находки по одной, в порядке важности: заголовок → «зачем» → границы → критерии. +Порядок такой, потому что заголовок и «зачем» — это всё, что видно в индексе, а +по индексу и выбирают. ``` <файл> @@ -181,11 +160,8 @@ color: green почему: <одна фраза> ``` -Отдельным блоком после находок — **строки «Завершения» без задач**, если такие -нашлись: цель, строка, и что это значит. - В конце — **границы покрытия**: сколько записей просмотрено из скольких, какие -цели открыты, какие не смотрел и почему. Отчёт без этой строки читается как +не смотрел и почему. Отчёт без этой строки читается как «беклог проверен», не сообщая, какая его часть осталась нетронутой. Туда же — строка «замечено не по моей части», если бросился в глаза язык; машинно проверяемое в неё **не идёт**. diff --git a/av-dev/agents/task-wording.md b/av-dev/agents/task-wording.md index b7098db..61ca8d4 100644 --- a/av-dev/agents/task-wording.md +++ b/av-dev/agents/task-wording.md @@ -1,18 +1,18 @@ --- name: task-wording -description: "Вычитка языка записей каталога задач по информационному стилю — задачи, цели, строки индексов и причины отказа. Смотрит отглагольные существительные и страдательный залог, оценку без факта, стоп-слова и канцелярит, «одна мысль — одно предложение», англицизм при живом русском слове, жаргон и метафоры вместо прямого называния, термин, которого нет в документах проекта, транслит в слаге. Отдаёт готовые формулировки на замену и ничего не правит сам. Форму записи (заголовок по типу, «зачем», границы, оракулы) смотрит отдельный агент task-form, документы проекта вычитывает doc-wording. Использовать после заведения или разбора пачки записей, до взятия в работу и на переоценке беклога. Только чтение." +description: "Вычитка языка записей каталога задач по информационному стилю — задачи, строки индекса и причины отказа. Смотрит отглагольные существительные и страдательный залог, оценку без факта, стоп-слова и канцелярит, «одна мысль — одно предложение», англицизм при живом русском слове, жаргон и метафоры вместо прямого называния, термин, которого нет в документах проекта, транслит в слаге. Отдаёт готовые формулировки на замену и ничего не правит сам. Форму записи (заголовок по типу, «зачем», границы, оракулы) смотрит отдельный агент task-form, документы проекта вычитывает doc-wording. Использовать после заведения или разбора пачки записей, до взятия в работу и на переоценке беклога. Только чтение." tools: Read, Grep, Glob model: sonnet color: green --- -Ты — **вычитка языка записей каталога задач**: задач, целей, строк индексов и -причин отказа. Оптика — слова и фразы, а не то, что запись описывает: ты не -судишь, нужна ли задача, верно ли выбрана цель и правильно ли запись оформлена. +Ты — **вычитка языка записей каталога задач**: задач, строк индекса и причин +отказа. Оптика — слова и фразы, а не то, что запись описывает: ты не судишь, +нужна ли задача и правильно ли она оформлена. Границу держи твёрдо, и она у тебя одна. **Форму записи** — тип, заголовок по -типу, «зачем» вместо пересказа, раздел «Затрагивает», годность оракулов, связь -со строкой «Завершения» цели — смотрит `task-form`, и тебе она не поручена даже +типу, «зачем» вместо пересказа, раздел «Затрагивает», годность оракулов — +смотрит `task-form`, и тебе она не поручена даже там, где бросается в глаза. Отсюда же исключение, и оно **против** тебя: неудачное слово **в заголовке** судит `task-form`, потому что заголовок целиком его. Увидел не по своей части — скажи одной строкой в конце доклада, не @@ -27,9 +27,8 @@ color: green ## Что тебе дают -Список записей или каталог задач: файлы `items/.md`, а с ними — индексы -(`BACKLOG.md`, `ROADMAP.md`, `REJECTED.md`), где та же запись представлена -строкой. **Строка индекса вычитывается наравне с файлом**: по ней запись +Список записей или каталог задач: файлы `items/.md`, а с ними — +`BACKLOG.md` и `REJECTED.md`, где та же запись представлена строкой. **Строка индекса вычитывается наравне с файлом**: по ней запись выбирают, не открывая тела, и «зачем» в ней повторяется дословно. Плюс, если зовущий их назвал, документы проекта — паспорт, архитектура, @@ -200,8 +199,8 @@ color: green критериев `check` только считает — поимённо их судит `tasks.py ready`, и это тоже не твоя находка: твоя — язык того, что уже написано. -**Содержание работы**: нужна ли задача, верно ли выбрана цель, не крупна ли она, -достаточна ли декомпозиция. Это разбор, и его ведёт человек со скиллом `task-track`. +**Содержание работы**: нужна ли задача, не крупна ли она, достаточна ли +декомпозиция. Это разбор, и его ведёт человек со скиллом `task-track`. **Полезное действие, параллельность и работающий заголовок** — тоже не твои. Они в доктрине языка, судит их человек: находка по ним требует увидеть текст diff --git a/av-dev/shared/axes.md b/av-dev/shared/axes.md index 713b4e8..926fbc3 100644 --- a/av-dev/shared/axes.md +++ b/av-dev/shared/axes.md @@ -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`, «Развилка» | | метка | `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`, «Тип записи» | | тип записи | метку и глубину — **не влияет, и это записано явно** | там же | | сценарий | режим прогона: обслуживание идёт без метки | `code-resolve/references/maintain.md` | @@ -44,7 +48,7 @@ | категория документа | заводит ли документ направление проверки | `canon.md`, «Три категории» | | severity | что с находкой делают дальше | `code-review/SKILL.md`, «Что происходит с находками» | -**Две клетки пусты, и это сказано намеренно, а не забыто.** +**Три клетки пусты, и это сказано намеренно, а не забыто.** **Категория документа × режим прогона.** На прогоне **с меткой** своя тема проекта закрыта при любом значении: `review-basics` — приёмник проектных тем и @@ -53,6 +57,11 @@ проекта в нём нет. Значит, документ, заведённый проектом как тема, на обслуживании не смотрит никто, и строкой это нигде не называется. +**Стадия проекта × метка.** Изменение на стройке ничем не проще того же +изменения на доработке: метку назначает разметка по факту изменения, и стадия в +неё не входит. Заманчивая мысль «на стройке всё `small`, потому что приложения +ещё нет» разбивается о первый же шаг, кладущий схему хранилища. + **Режим прогона × severity.** Триаж обязателен всегда, в том числе без метки. Но часть оснований `critical` — построенный путь к отказу, замер — добывается проходами, которые без метки не запускаются. Значит ли это, что `critical` на diff --git a/av-dev/shared/config.py b/av-dev/shared/config.py index 5982f3a..d3a673b 100644 --- a/av-dev/shared/config.py +++ b/av-dev/shared/config.py @@ -26,9 +26,9 @@ [tasks] dir = "tasks" # каталог задач от корня репозитория + stage = "build" # стадия проекта: build | support items = "items" # имена частей каталога — необязательны backlog = "BACKLOG.md" - roadmap = "ROADMAP.md" Коды выхода зовущих скриптов — общий словарь av-dev; отсюда возвращается исключение `ConfigError`, а решает по нему вызывающий. @@ -56,7 +56,7 @@ LEGACY_TASKS = ".tasks.json" # Версия раскладки — одна на плагин. Журнал версий — references/changelog.md # скилла `canon`, повышает его операция `upgrade`. -VERSION = 2 +VERSION = 3 VERSION_KEY = "version" @@ -280,6 +280,33 @@ def merge_section(root: Path, name: str, values: dict) -> list[str]: 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: """Ключи секции, которые уже есть и разошлись с тем, что мы собирались дать. @@ -317,7 +344,12 @@ def skeleton(number: int, docs: dict | None = None, tasks: dict | None = None) - out += ["", "[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): out.append(f"{key} = {quote(tasks[key])}") return "\n".join(out) + "\n" diff --git a/av-dev/shared/operations.md b/av-dev/shared/operations.md index 233ec03..1ca9c65 100644 --- a/av-dev/shared/operations.md +++ b/av-dev/shared/operations.md @@ -1,7 +1,7 @@ # Сопровождение и эксплуатация **Это дом.** Словарь «чем держат проект» назван в трёх местах трёх разных -скиллов: секция `Сопровождение` в роадмапе (`task-track`), раздел «Эксплуатация» +скиллов: задачи типа `chore` (`task-track`), раздел «Эксплуатация» в `architecture.md` (`canon`) и тема ревью `operations` (`code-review`). Ни один из трёх им не владеет, поэтому дом стоит в `shared/`. @@ -18,16 +18,18 @@ | Место | Уровень | Что там | | --- | --- | --- | -| `ROADMAP.md`, секция `Сопровождение` | план | **работы**, которые собираемся делать: цели и их задачи | +| `BACKLOG.md`, задачи `chore` | план | **работы**, которые собираемся делать | | `architecture.md`, раздел «Эксплуатация» | состояние | **как устроено сейчас**: где работает, что рядом, кто перезапускает | | тема ревью `operations` | оптика | **чем проверяем**: «это упало через неделю на проде» | Слово **«поддержка» не употребляется вовсе** — в нём слышится помощь -пользователю, а это другая работа. +пользователю, а это другая работа. По той же причине им не названа и **стадия +проекта**: у неё имя «доработка» (`support`), словарь — `task-track`, «Две +стадии». **Граница с возможностями проходит по тому, кто наблюдает.** «Приложение сообщает о своём состоянии» — возможность приложения, её место среди прочих -целей: наблюдает пользователь сервиса. «Дежурный видит состояние на одном -экране» — сопровождение: наблюдаем мы. Одни и те же метрики попадают в разные -секции роадмапа, и это верно — секции отвечают на разные вопросы. +задач: наблюдает пользователь сервиса. «Дежурный видит состояние на одном +экране» — сопровождение: наблюдаем мы. Одни и те же метрики попадают в задачи +разных типов, и это верно — типы отвечают на разные вопросы. diff --git a/av-dev/skills/canon/references/canon.md b/av-dev/skills/canon/references/canon.md index 5738498..844fa2a 100644 --- a/av-dev/skills/canon/references/canon.md +++ b/av-dev/skills/canon/references/canon.md @@ -29,7 +29,7 @@ ## Сопровождение и эксплуатация — целое и часть Словарь этой темы — [shared/operations.md](../../../shared/operations.md): -целое и часть, три места одной темы (секция роадмапа, раздел архитектуры, тема +целое и часть, три места одной темы (задачи `chore`, раздел архитектуры, тема ревью `operations`) и граница с возможностями проекта. Здесь он не пересказывается: копия жила рядом с домом в одном дереве и была ровно тем вторым домом, против которого правило и написано. @@ -357,36 +357,35 @@ kebab-case.** Причина не эстетическая: имя файла с вовсе, и отказом это быть не может. Раскладку, форму записи и команды держит скилл `av-dev:task-track`. Ниже — то, -от чего зависит, читается ли проект как продукт: канон высказывается об этом -потому, что роадмап отвечает на вопрос о **системе**, а не о работах. +от чего зависит, читается ли проект как продукт. -**`ROADMAP.md` отвечает на «что приложение уже умеет и чего ещё не умеет».** Это -не очередь работ: цель — **возможность приложения**, задача — шаг к ней. -Достигнутая цель из роадмапа **не исчезает** — строка с датой переезжает в -секцию достигнутого, потому что «что умеет» и есть половина вопроса, ради -которого документ открывают. Вторым домом поведения роадмап при этом не -становится: нормативное поведение живёт в `openspec/specs/`, роадмап отвечает, -**когда и в каком порядке** оно появилось. +**Индекс работ один — `BACKLOG.md`, и «что приложение уже умеет» он не +отвечает.** На этот вопрос отвечают `openspec/specs/` (нормативное поведение) и +`git log` индекса (когда и в каком порядке оно появилось). Роадмапа в каноне +нет: половину своего вопроса он дублировал беклогом, а вторую — спеками. -**У каждой записи есть тип, и тип решает, что с ней можно делать.** Дом типа — +**У проекта есть стадия, и она решает, что значит порядок строк беклога:** +`build` — зависимость, `support` — важность. Канон её называет, потому что от +неё зависит, читается ли список работ как план стройки или как очередь правок; +механика — `task-track`, «Две стадии». + +**У каждой задачи есть тип, и тип решает, что с ней можно делать.** Дом типа — поле меты `Тип` первой строкой; эмодзи в заголовке от него производна. Словарь закрыт: | Тип | Что это | | --- | --- | -| 🎯 `goal` | возможность приложения | | ✨ `feature` | снаружи появляется то, чего не было | | 🐞 `fix` | поведение расходится с заявленным | | 🧹 `chore` | обслуживание, поведение не меняется | | 🔬 `research` | исход — знание, а не изменение | -**Схемы записи здесь нет намеренно.** Какие разделы тип требует, нужна ли ему -цель и берётся ли он в работу — скилл `av-dev:task-track`, раздел «Тип -записи», подробно — по файлу на тип в его `references/task-<тип>.md`. Канон -фиксирует **словарь**, потому что -от него зависит, читается ли проект как продукт; схема — механика ведения задач, -и второй её экземпляр разошёлся бы с первым (он и разошёлся: канон успел -объявить цель у `fix` запрещённой, хотя она там необязательна). +**Схемы записи здесь нет намеренно.** Какие разделы тип требует — скилл +`av-dev:task-track`, раздел «Тип записи», подробно — по файлу на тип в его +`references/task-<тип>.md`. Канон фиксирует **словарь**, потому что от него +зависит, читается ли проект как продукт; схема — механика ведения задач, и второй +её экземпляр разошёлся бы с первым (он и разошёлся: канон успел объявить цель у +`fix` запрещённой, хотя она была необязательна, — и целей теперь нет вовсе). Схема требуется **к взятию в работу**, а не к заведению: беклог пополняется чаще, чем разбирается, и требование на входе выгоняло бы в заметки то, что должно @@ -454,7 +453,7 @@ kebab-case.** Причина не эстетическая: имя файла с | почему решено так | `adr/`, источник — архивный `design.md` либо записка разведки | | граница домена, «чем не является» | `passport.md` | | инвариант и его severity | `CLAUDE.md` | -| что приложение умеет и чего не умеет; порядок работ | `tasks/ROADMAP.md` | +| что осталось сделать и в каком порядке | `tasks/BACKLOG.md` | | измеренное число | `research/` | | настройка с числовым значением | `database.md` | | периметр и модель угроз | `security.md` | @@ -488,8 +487,8 @@ kebab-case.** Причина не эстетическая: имя файла с | --- | --- | | `docs/review-brief.md` | документы канона и есть бриф; остаток — в `review.md` | | `docs/specs/` | `openspec/specs/` (поведение) и `architecture.md` (обзор) | -| `docs/drafts/` | идея → запись `research`; отказ → ADR; порядок → `ROADMAP.md`; размышление → `opsx:explore` | -| `docs/plan.md` | `tasks/ROADMAP.md` | +| `docs/drafts/` | идея → запись `research`; отказ → ADR; порядок → `BACKLOG.md`; размышление → `opsx:explore` | +| `docs/plan.md` | `tasks/BACKLOG.md` | | `BRIEF.md` | `passport.md` | | `docs/backlog/` | `tasks/` в корне репозитория | | `docs/review-journal.md`, `docs/review/journal.md` | `docs/review.md` | diff --git a/av-dev/skills/canon/references/changelog.md b/av-dev/skills/canon/references/changelog.md index 905d237..938f62c 100644 --- a/av-dev/skills/canon/references/changelog.md +++ b/av-dev/skills/canon/references/changelog.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 Скилл `doc-canon` стал `canon`: префикс называл материал (`doc-`), а скилл diff --git a/av-dev/skills/canon/references/skeletons.md b/av-dev/skills/canon/references/skeletons.md index 2a93c16..c0d1fe2 100644 --- a/av-dev/skills/canon/references/skeletons.md +++ b/av-dev/skills/canon/references/skeletons.md @@ -32,7 +32,7 @@ # Паспорт проекта Зачем это и для кого. [architecture.md](architecture.md) отвечает «как -устроено», [tasks/ROADMAP.md](../tasks/ROADMAP.md) — «в каком порядке», паспорт — +устроено», [tasks/BACKLOG.md](../tasks/BACKLOG.md) — «что осталось», паспорт — «зачем и для кого». ## Цель @@ -261,7 +261,7 @@ ## Последствия - `+` что стало лучше. -- `−` чем платим: ограничения, риски, нагрузка на поддержку. +- `−` чем платим: ограничения, риски, нагрузка на сопровождение. ``` ## `docs/review.md` diff --git a/av-dev/skills/canon/scripts/docs.py b/av-dev/skills/canon/scripts/docs.py index 275bf79..5b3f805 100644 --- a/av-dev/skills/canon/scripts/docs.py +++ b/av-dev/skills/canon/scripts/docs.py @@ -127,10 +127,10 @@ NOT_DOCS = {".docs.json", ".pm.json", "tasks"} RETIRED = { "review-brief.md": "документы канона и есть бриф; остаток — в review", "review-journal.md": "→ документ review", - "plan.md": "→ tasks/ROADMAP.md (ведёт скилл task-track)", + "plan.md": "→ tasks/BACKLOG.md (ведёт скилл task-track)", "local-research.md": "→ документ research", "specs": "поведение → openspec/specs/, обзор → тема architecture", - "drafts": "идея → запись research, отказ → ADR, порядок → ROADMAP.md", + "drafts": "идея → запись research, отказ → ADR, порядок → BACKLOG.md", "backlog": "→ tasks/ в корне репозитория (ведёт скилл task-track)", } diff --git a/av-dev/skills/doc-init/SKILL.md b/av-dev/skills/doc-init/SKILL.md index 9545af6..9029fc2 100644 --- a/av-dev/skills/doc-init/SKILL.md +++ b/av-dev/skills/doc-init/SKILL.md @@ -1,6 +1,6 @@ --- 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»: «архитектуры пока нет: кода нет, заводится первой задачей». Проход читает её как факт. -**`tasks/ROADMAP.md` в таблице нет намеренно.** Первые цели `init` собирает -интервью (блок 6), но записывает их не он: каталогом задач и формой целей владеет -`av-dev:task-track`, и это шаг 7. Человек от каталога задач отказался — цели -остаются списком в докладе, роадмапа в проекте не появляется, и это говорится +**`tasks/BACKLOG.md` в таблице нет намеренно.** Первый план работ `init` +собирает интервью (блок 6), но записывает его не он: каталогом задач владеет +`av-dev:task-track`, и это шаг 7. Человек от каталога задач отказался — план +остаётся списком в докладе, беклога в проекте не появляется, и это говорится строкой. ## Порядок интервью — зависимость, а не удобство @@ -54,9 +54,11 @@ description: "Завести новый проект — сессия вопро проекте нельзя откатить — деплой, выкладка наружу, перезапись данных. 5. **Чем краснеет гейт.** Какие проверки обязательны; что красит безусловно; чего в гейте намеренно не будет и кто тогда это гоняет. -6. **Первые цели.** Возможности приложения, а не задачи: три-пять целей в - `Запланировано`, каждая — ответ на «что приложение будет уметь», с - обоснованием очереди прозой. +6. **Первые шаги стройки.** Новый проект по определению начинается со стадии + `build`: приложения ещё нет. Собери **список от базы к деталям** — что нужно + сделать, чтобы приложение заработало, — и обоснуй **порядок**: он значит + зависимость, а не важность. Пять-десять шагов достаточно: план дописывается + по ходу стройки, и это законно. ### Как вести @@ -135,9 +137,9 @@ description: "Завести новый проект — сессия вопро первом же уточнении. 6. Заведи скелет остальных по [скелетам](../canon/references/skeletons.md) — каждый с честной строкой. -7. Каталог задач и первые цели — **вызови скилл `av-dev:task-track`**: он владеет - форматом целей и задач. Не разрешился — учёт задач остаётся владельцу, и это - тоже строка доклада. +7. Каталог задач и первые шаги — **вызови скилл `av-dev:task-track`**: он владеет + форматом задач и стадией (`init --stage build`). Не разрешился — учёт задач + остаётся владельцу, и это тоже строка доклада. 8. `docs.py check` из скилла `canon` — до отсутствия дрейфа. Замечания о незаполненных плейсхолдерах остаются: их закрывает не `init`, а работа. 9. **Вычитай написанное — агент `doc-wording`**, по пачке заполненных документов diff --git a/av-dev/skills/task-groom/SKILL.md b/av-dev/skills/task-groom/SKILL.md index 746fb66..c4848d5 100644 --- a/av-dev/skills/task-groom/SKILL.md +++ b/av-dev/skills/task-groom/SKILL.md @@ -27,7 +27,7 @@ description: "Груминг беклога — интерактивный ра ## Три правила, из которых всё следует 1. **Порядок назначает человек, машина его не выводит.** Ни давность, ни тип, ни - число задач под целью приоритетом не являются. Единственное место в очереди, + размер секции приоритетом не являются. Единственное место в очереди, назначенное не человеком, — конец секции у сырья, и оно из очереди изъято (`task-track`, правило 4). 2. **Порция важнее охвата.** Тридцать задач за заход — это усталость и @@ -37,6 +37,28 @@ description: "Груминг беклога — интерактивный ра `--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. Сперва то, что решается фактом и не требует ничьего суждения (сделано попутно, отменено решением, дублируется, симптомы одного дефекта), затем то, что решает человек (жива ли, -та ли цель, задача ли это ещё). +задача ли это ещё). **4. Что важно сейчас.** Расстановка порядка — `move --after <слаг>` и `move --first`. Разбирается **не весь беклог, а верх очереди**: первые три-пять @@ -146,15 +168,15 @@ flowchart TD кодом стоит меньше, чем та же работа через квартал; - **что дорожает от ожидания** — данные копятся, миграция усложняется, внешний срок приближается; -- **цель, которую человек назвал следующей.** +- **то, что человек назвал следующим.** Довод **записывается причиной** (`move --after <слаг> --reason …`). Порядок без причины — это порядок, который на следующем груминге назначат заново с нуля. -**Цель и приоритет — независимые оси.** Очередь может идти поперёк целей, и это -законно: задачи одной цели не обязаны стоять подряд. Но если под целью годами -ничего не поднимается наверх — это разговор про цель, а не про очередь, и он -идёт на шаге 3. +**Тема и приоритет — независимые оси.** Очередь может идти поперёк полок, и это +законно: задачи одной темы не обязаны стоять подряд. Но если из одной полки +годами ничего не поднимается наверх — это разговор про саму работу, а не про +очередь, и он идёт на шаге 3. ## Документы устаревают тем же ходом работы @@ -231,11 +253,11 @@ flowchart TD - Что просмотрено: N из M, сколько порций, по какому признаку отобраны. - Вопросы: разобрано N, из них отвечено без человека N, снято тегов N. - **Что перестало быть важным**: удалено как реализованное (со ссылками), ушло - без реализации (с причинами), понижено до сырья, слито, сменило тип или цель. + без реализации (с причинами), понижено до сырья, слито, сменило тип. - **Что важно сейчас**: верх очереди по каждой секции — слаги в порядке, и по каждому движению довод одной строкой. -- **Границы покрытия**: сколько задач не трогали и какие именно секции, теги или - цели остались — иначе доклад читается как «беклог разобран». +- **Границы покрытия**: сколько задач не трогали и какие именно секции или теги + остались — иначе доклад читается как «беклог разобран». - `tasks.py check` после правок — результат строкой. ## Чего этот скилл не делает diff --git a/av-dev/skills/task-groom/references/portions.md b/av-dev/skills/task-groom/references/portions.md index ca6d3a5..0338aa3 100644 --- a/av-dev/skills/task-groom/references/portions.md +++ b/av-dev/skills/task-groom/references/portions.md @@ -41,8 +41,8 @@ проверки на нужность. Свежесть меряется git'ом, как и залежалость, — датой появления файла в истории; 2. дальше **по залежалости** — `list --stale`; - 3. по потребности — одна секция целиком, один тег (партия ревью), одна цель - (`--goal`), список от человека. + 3. по потребности — одна секция целиком, один тег (партия ревью), список от + человека. - **Останавливайся на границе порции**, даже если «ещё чуть-чуть осталось». Между порциями — промежуточный доклад. @@ -84,20 +84,14 @@ 6. **Жива ли она вообще.** Контекст мог измениться: ушла зависимость, отпал сценарий, обошли иначе. Здесь и звучит вопрос, выкидывать ли. -7. **Та ли цель — и нужна ли она вообще.** `feature`, которой не находится цель, - — кандидат на выход: новая возможность вне цели это возможность, которой никто - не заказывал. Операционной задаче (`fix`, `chore`, `research`) цель не нужна, - и выдумывать её здесь не надо. - - **Отменяется и сама цель** — когда замысел оказался неверен, а не когда - задача выбрала не ту. Тогда порция расширяется до всех задач этой цели: каждую - либо закрыть своей причиной, либо перевесить на другую цель, и только потом - закрыть цель. Порядок и почему он такой — - [task-goal.md](../../task-track/references/task-goal.md#отменённая-цель--сперва-задачи-потом-цель). +7. **Тот ли тип.** Заводилась починкой, а после разбора оказалось, что + поведение никогда и не было заявлено, — это `feature`. Тип, оставшийся от + прошлой формулировки, врёт ровно там, где по нему отбирают, и требует не тех + разделов. 8. **Задача ли это по-прежнему.** Не проходит `ready` по существу, а не по недописанным разделам → `edit --type research` и опустошённый раздел - «Вопрос», то есть сырьё; дальше штурм. Разрослась → это несколько задач под - той же целью, дальше декомпозиция. + «Вопрос», то есть сырьё; дальше штурм. Разрослась → это несколько задач, + дальше декомпозиция. 9. **Не подешевела ли она.** Сделанная с прошлого раза работа меняет цену **других** задач: рядом с только что тронутым кодом та же работа стоит меньше. Это довод и на шаге 4 — задача, внезапно подешевевшая, поднимается в очереди @@ -111,7 +105,7 @@ нигде не хранится. Задача из верхних строк `--stale`, которую и этот заход оставляет без изменений, -**либо двигается (меняет цель, поднимается в очереди, уходит с причиной), либо +**либо двигается (меняет полку, поднимается в очереди, уходит с причиной), либо остаётся с явно записанной причиной**, почему её держим (`move --reason …` — без `--section` секция берётся текущая). Молчаливое «оставить как есть» на давно неподвижной задаче — это решение не принимать решение; запись причины @@ -123,9 +117,8 @@ Разбирается **верх очереди**, а не весь беклог: первые три-пять строк каждой секции. Ниже пятой строки порядок всё равно перестаёт что-либо значить. -1. **Покажи текущий верх** — `list --index backlog`, по секциям, в том порядке, - в каком строки лежат. Плюс состояние проекта из роадмапа: секция `Готово` - отвечает на «где мы», `Запланировано` — на «куда шли». +1. **Покажи текущий верх** — `list`, по секциям, в том порядке, в каком строки + лежат. 2. **Спрашивай сравнением, а не оценкой.** «Что из этих двух делают раньше» имеет проверяемый ответ, «насколько важна эта задача» — нет. Веди попарно и сверху: что первое, что после него. @@ -133,7 +126,7 @@ или `move --first --reason …`. Довод берётся из перечня в [SKILL.md](../SKILL.md#приоритет-как-его-расставляют): сломано сейчас, разблокирует остальное, дешевеет от сделанного, дорожает от ожидания, - названная цель. + названо человеком. 4. **Проверь верх на готовность** — `tasks.py ready <слаг> …` по первым строкам. Задача, стоящая первой и не проходящая `ready`, — это очередь, которая врёт: взять её нельзя. Либо дописывается здесь же, либо уступает место. diff --git a/av-dev/skills/task-track/SKILL.md b/av-dev/skills/task-track/SKILL.md index 9a8dced..9faf767 100644 --- a/av-dev/skills/task-track/SKILL.md +++ b/av-dev/skills/task-track/SKILL.md @@ -1,13 +1,13 @@ --- 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/.md` плюс -строка **ровно в одном** индексе. Скилл владеет **форматом и содержимым**: -заводит, редактирует, закрывает, разбирает находки ревью, дробит, штурмует сырьё. +Задачи — каталог markdown-файлов. Одна задача = один файл `items/.md` плюс +строка в `BACKLOG.md`. Скилл владеет **форматом и содержимым**: заводит, +редактирует, закрывает, разбирает находки ревью, дробит, штурмует сырьё. Чем он **не** владеет: **очередью** — что делать следующим и что перестало быть важным, решает скилл `task-groom`, а этот скилл лишь даёт ему операции; и выполнением @@ -17,57 +17,53 @@ description: Ведение задач и целей как каталога mar Ситуация не покрыта инструкцией — решай по ним. -0. **Цель — возможность приложения, задача — шаг к ней.** Цель отвечает на «что - приложение будет уметь», её «Завершение» — наблюдаемый признак того, что уже - умеет; задача отвечает на «что для этого нужно сделать». Оценивается проект - по **поведению**, а не по внутреннему устройству, поэтому и роадмап отвечает - не «сколько работ осталось», а «что уже умеет и чего ещё не умеет». - Свойство поведения — тоже возможность: «сообщает о своём состоянии», - «исход слияния не зависит от порядка доставки» — законные цели. -1. **Беклог гниёт с той стороны, где его пополняют.** Заведение — самая частая - операция и с худшим отказом: из одного разговора рождается пять файлов, а - переоценка потом разгребает то, чего не надо было заводить. Дедупликация и - фильтр на входе дешевле любой чистки. Заводим только то, что **не делаем - сейчас** и о потере чего пожалеем. -2. **Файл — источник истины, индексы производны.** Разошлись — неправы индексы. +0. **Стадия решает, что значит порядок строк.** Проект живёт в одной из двух + стадий, и обе ведут один и тот же беклог, но читают его по-разному. + На **стройке** (`build`) беклог это план от базы к деталям: порядок — + зависимость, «раньше нельзя». На **доработке** (`support`) беклог это очередь + правок: порядок — важность, «раньше лучше». Из этого следует остальное — + сколько у беклога секций, как его пополняют, что значит его опустошение и + нужен ли груминг. Стадия объявлена ключом `[tasks] stage`; молчание ответом + не считается, и `check` без неё отказывает. +1. **Беклог гниёт с той стороны, где его пополняют — на доработке.** Заведение + там самая частая операция и с худшим отказом: из одного разговора рождается + пять файлов, а переоценка потом разгребает то, чего не надо было заводить. + Дедупликация и фильтр на входе дешевле любой чистки: заводим только то, что + **не делаем сейчас** и о потере чего пожалеем. + + **На стройке правило не применяется**, и это не послабление. Список стройки + пишется вперёд целиком — он и есть замысел, — а фильтр «не заводи то, чего не + делаешь сейчас» запретил бы написать план дальше первого шага. Дедуп остаётся + в обеих стадиях: две записи об одном плохи всегда. +2. **Файл — источник истины, индекс производен.** Разошлись — неправ индекс. Согласованность механизируема и проверяется командой, а не вниманием: всё, что ловит `tasks.py check`, не должно попадать ни в чек-лист, ни в промпт. Поэтому **«зачем» живёт в мете файла**, а строка индекса его лишь повторяет: пока поле лежало только в индексе, восстановление пропавшей - строки теряло его молча и навсегда. Единственное исключение намеренное: **в каком - индексе лежит запись, знают индексы** — поля-состояния в файле нет. И - **порядок строк в беклоге**: приоритет это свойство очереди, а не задачи, и в - файле ему места нет (правило 4). + строки теряло его молча и навсегда. Единственное исключение намеренное: + **порядок строк в беклоге** — он свойство списка, а не задачи, и в файле ему + места нет (правило 4). 3. **Причина переживает запись.** Выкинутая без причины задача вернётся через квартал тем же текстом. Реализованная оставляет след в коммите — выкинутая не оставляет ничего, поэтому у неё есть `REJECTED.md`. -4. **Приоритет — это порядок строк, а цель есть не у всякой задачи.** Очередь - внутри секции беклога значима: **первая строка — то, что делают следующим**. - Приоритет назначает человек на груминге, машина его не выводит и не угадывает. +4. **Порядок строк — единственное, чего в файле нет.** Он значим в обеих + стадиях, и назначает его человек: на стройке — раскладывая шаги по + зависимости, на доработке — на груминге. Машина порядок не выводит и не + угадывает; всё, что она делает сама, — ставит машинную позицию в **конец** + секции и говорит об этом вслух. - Прежде здесь стояло «порядка нет, есть цель», и обосновано это было тем, что - на «что делать дальше» отвечает **набор спринта**. Набора больше нет, а - вопрос остался — и без порядка отвечать на него стало нечем. - - **Дом приоритета — индекс, а не файл.** Это то же исключение из правила 2, - что и «в каком индексе лежит запись»: приоритет — свойство очереди. Положи он - в файл числом, и два соседних файла смогли бы утверждать одно и то же место, - а строка индекса — противоречить обоим. - - Цель обязательна там, где она и есть содержание работы, — у **новой - возможности** (`feature`). Починка, техдолг и разведка служат - работоспособности, а не направлению, и живут без цели законно. Придуманная им - цель — то же враньё, от которого спасает тип. **Цель и приоритет — - независимые оси:** очередь может идти поперёк целей, и это законно. + **Дом порядка — индекс, а не файл.** Положи его в файл числом, и два соседних + файла смогли бы утверждать одно и то же место, а строка индекса — + противоречить обоим. Одно место в очереди назначено **не человеком, а типом**: **сырьё** - (`research` без раздела «Вопрос») стоит в конце своей категории. Его не берут, + (`research` без раздела «Вопрос») стоит в конце своей секции. Его не берут, и между берущимся оно каждый раз требует открыть файл, чтобы это понять. Раз это выводится, проверяет и чинит это машина. -5. **Тип решает, что с записью можно делать.** Тип — единственная ось и первое - поле меты: от него зависит, какие разделы обязательны в теле, нужна ли цель, - берётся ли запись в работу и в каком индексе живёт её строка. Словарь закрыт; - ни один тип не подошёл — значит, в записи их два, и её надо разделить. +5. **Тип решает, что с задачей можно делать.** Тип — вторая ось и первое + поле меты: от него зависит, какие разделы обязательны в теле и берётся ли + запись в работу. Словарь закрыт; ни один тип не подошёл — значит, в записи их + два, и её надо разделить. ## Раскладка @@ -79,55 +75,23 @@ description: Ведение задач и целей как каталога mar ``` tasks/ - items/ задачи и цели файлами, .md, слаги английские - ROADMAP.md состояние проекта: что уже умеет и чего ещё не умеет - BACKLOG.md что можно взять — только задачи, целей здесь нет. - Порядок строк в секции значим: это очередь + items/ задачи файлами, .md, слаги английские + BACKLOG.md что можно взять. Порядок строк в секции значим, + и значит он разное на разных стадиях REJECTED.md ушедшее БЕЗ реализации, с причиной и датой ``` -Правило, снимающее путаницу: **`BACKLOG.md` — то, что берут; `ROADMAP.md` — то, -подо что берут.** Цель в работу взять нельзя — берут её задачи, — поэтому в -списке берущихся ей не место. +**Индекс один.** `REJECTED.md` индексом не считается: он не говорит, где запись +числится, — это кладбище ушедшего. -**Четыре секции роадмапа, и последняя отвечает на половину вопроса:** +**Секции беклога называет проект**, и `check` проверяет у них ровно две вещи: +что секция есть хоть одна и что на стройке она **одна**. Смысла секции не несут +— это полки домена (`Ядро`, `Инфра`), — и подгонять их имена под свой вкус +скрипт права не имеет. Поле меты, называющее полку, зовётся **Категория**. -| Секция | Англ. | Что в ней | -| --- | --- | --- | -| `Запланировано` | `Planned` | очередь значима и обосновывается прозой рядом | -| `Направления` | `Directions` | очереди нет, тянутся долго | -| `Сопровождение` | `Operations` | чем держат проект: инструмент, процесс, эксплуатация — не возможности приложения, и потому отдельно | -| `Готово` | `Done` | достигнутые цели — строкой с датой, **без ссылки на файл**: файл удалён, поведение живёт в спеках | - -**Порядок тоже канонический, и `Готово` стоит последним не из скромности.** -Достигнутое **копится**: через год этой секции больше, чем всех остальных -вместе. Стоя первой, она отодвигает за экран ровно то, ради чего роадмап -открывают чаще всего, — что делается сейчас и что дальше. Порядок проверяет -`check`, переставляет `check --fix`. - -**Секции роадмапа канонические, категории беклога — нет**, и разница не в любви к -единообразию. У каждой секции роадмапа свой смысл, в достигнутое пишет сам `close`, и -роадмап, названный по-своему, читался бы только своим автором. Категории беклога -(`Ядро`, `Инфра`) смысла не несут — это полки домена, и остаются делом проекта. -Отсюда и разные имена поля меты: у цели **Секция** (часть роадмапа — состояние -очереди), у задачи **Категория** (полка домена, на которой она лежит). - -Отсюда четыре правила, которые проверяет `tasks.py check`: **состав закреплён** -(чужая секция — ошибка, а не вольность), **все четыре обязаны быть** (нет -секции — нет ответа на её часть вопроса), **язык один на весь индекс**, **порядок -канонический**. `--roadmap-sections` у `init` нет: выбирать нечего. - -**Заголовок секции отбит пустой строкой с обеих сторон и написан с прописной.** -Во всех индексах одинаково, включая категории беклога, которые проект называет сам. -Написание канонических секций правит `check --fix` (заодно и ссылку на секцию в -мете файлов: имя секции принадлежит заголовку индекса, файл на неё только -ссылается); отбивку и порядок он правит везде. - -Оговорка про `Сопровождение`: слово `окружение` сюда не годится — в -`architecture.md` оно уже значит боевое окружение приложения, и одно слово в двух -смыслах развело бы документы канона. А `Разработка`, стоявшая тут раньше, -называла слишком много: роадмап **весь** про разработку, и секция с таким именем -не отличалась от остальных ничем. +**Заголовок секции отбит пустой строкой с обеих сторон и написан с прописной**; +отбивку правит `check --fix`. Написание секции в мете файлов он тоже правит: имя +секции принадлежит заголовку индекса, файл на неё только ссылается. **Секции «блокеры» в беклоге нет.** Блокер — это *состояние* (работа не может продолжаться ни одной задачей), а не полка: он живёт ровно до ответа человека, и @@ -138,53 +102,31 @@ tasks/ который переезжает с такой секцией, её надо удалить** — это единственное место, где это сказано. -**Запись живёт в одном индексе за раз.** Индексов два, и выбирает между ними -тип: цель в роадмапе, задача в беклоге. Сменился тип — строка переезжает -(`edit --type`). Файл в `items/` при этом **не двигается**: он и есть запись, -индексы лишь показывают, где она числится и в каком порядке стоит. - -**Порядок строк в беклоге — приоритет**, и он единственное, чего в файле нет -(правило 4). Отсюда следствие для всякой машинной правки индекса: -восстановленная или перенесённая строка встаёт **в конец своей секции**, и -скрипт об этом говорит. Молчаливая вставка выдала бы машинную позицию за -решение человека — а решение это его. +**Порядок строк — единственное, чего в файле нет** (правило 4). Отсюда следствие +для всякой машинной правки индекса: восстановленная или перенесённая строка +встаёт **в конец своей секции**, и скрипт об этом говорит. Молчаливая вставка +выдала бы машинную позицию за решение человека — а решение это его. **У сделанной задачи записи не остаётся** — файл и строка удаляются (`close --implemented`). Ей хватает коммита и документации проекта; вторая запись была бы вторым домом для того же факта. Вопрос «что было сделано и когда» отвечается -даром: индексы лежат под git, а закрытие коммитится отдельным коммитом учёта — +даром: индекс лежит под git, а закрытие коммитится отдельным коммитом учёта — `git log -p tasks/BACKLOG.md` отдаёт историю без отдельного журнала. -**У достигнутой цели запись остаётся, и это единственное исключение.** Файл -удаляется так же, а строка переезжает в секцию `Готово` с датой. Причина в том, -что цель — не работа, а **возможность**: «что приложение умеет» это половина -вопроса, ради которого роадмап и открывают, и стирать её вместе с файлом значит -оставить инструмент, отвечающий только «что осталось». Вторым домом это не -становится: поведение живёт в `openspec/specs/`, а роадмап отвечает **когда и в -каком порядке оно появилось** — другой вопрос. Ссылки на файл в строке нет -намеренно: файл удалён, а битая ссылка — законная ошибка `check`. - Куда запись может переехать и какой командой — весь набор переходов: ```mermaid stateDiagram-v2 state "BACKLOG.md — что берут" as B - state "ROADMAP.md — подо что берут" as P state "REJECTED.md — ушла без реализации" as R state "записи нет — реализована" as D - state "ROADMAP.md, «умеет» — цель достигнута" as A [*] --> B: add --type feature|fix|chore|research - [*] --> P: add --type goal - B --> P: edit --type goal --section - P --> B: edit --type feature|fix|chore|research --section + B --> B: move --after | --first | --section B --> D: close --implemented - P --> A: close --implemented B --> R: close --reason - P --> R: close --reason D --> 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` его не подставляет: +какая стадия у проекта, знает человек, а подставленное умолчание соврало бы ровно +там, где по нему принимают решение. -**Граница проходит по тому, кто наблюдает, а не по теме.** «Приложение сообщает -о своём состоянии» — возможность: наблюдает пользователь сервиса, и цели место -среди прочих. «Дежурный видит состояние на одном экране» — сопровождение: -наблюдаем мы. Одна и та же наблюдаемость попадает в разные секции, и это верно — -секции отвечают на разные вопросы. +**Секций на стройке одна, и это не педантизм.** Порядок там — зависимость, и +разложенный по полкам список перестаёт быть планом: два шага из разных секций +уже не сравнить. На доработке полки законны — правки независимы, и очередь +внутри полки самостоятельна. -**Сопровождение и эксплуатация — целое и часть**, а не синонимы, и та же тема -живёт ещё в двух местах: разделе «Эксплуатация» в `architecture.md` и теме ревью -`operations`. Словарь у всех трёх общий, и дом у него один: -[shared/operations.md](../../shared/operations.md) — читается по ссылке. -Пересказывать его своими словами нельзя: три перечня «чем держат проект» уже -разъезжались на «метриках и логах» против «мониторинга». +**Переход — событие, а не настройка.** `tasks.py stage support` переносит остаток +беклога в первую новую секцию, правит «Категорию» в файлах и говорит, что порядок +с этого момента значит другое. Датой ему служит коммит: отдельного журнала ради +одной строки не заводится. Признак созревания наблюдаемый — беклог стройки +исчерпан, и `check` об этом напоминает; **запретить переход раньше скрипт не +берётся**: «приложение построено» решает человек, а не счётчик строк. -Секция выбирается так: очередь значима и обоснована прозой — `Запланировано`; -тянется долго и очереди не имеет — `Направления`; не про приложение, а про то, -чем его держат, — `Сопровождение`; в `Готово` кладёт сам `close`. +Обратный переход (`stage build`) разрешён и устроен так же. Он редок — проект +уходит на стройку заново разве что при переделке замысла целиком, — но +запрещать его было бы запретом на то, что иногда и правда случается. -- **Список задач цели выводится, а не хранится.** В теле цели — зачем она и что - считается её завершением; перечня задач там нет. Он был бы третьим индексом и - поехал бы на первой же закрытой задаче, а `check` про него не знает. Связь - однонаправленна: задача несёт тег `goal:<слаг>`, перечень даёт - `tasks.py list --goal <слаг>`. -- **Статус цели выводится.** Цель достигнута, когда у неё не осталось открытых - задач; `[x]`/`[~]` руками не ведутся, а `close` цели с живыми задачами - скрипт запретит. Достижение — `close <цель> --implemented`: файл удаляется, - строка с датой переезжает в `Готово`. Ошиблись — `reopen` вернёт файл и - **снимет строку достигнутого**, чтобы роадмап не утверждал того, чего нет. Единственная оговорка: цель без задач неотличима — «ещё не - разобрана» или «всё закрыто». Различает **тег `decomposed`** в мете - цели: он ставится, когда цель разложена на задачи. Тег, а не строка в теле — - потому что проверяется механически: `check` **напоминает** о нём у пустой цели - (замечанием, не ошибкой — неразобранная цель это законное состояние), а `check - --fix` сам проставляет его цели, у которой задачи есть. -- **Тип `[epic]` упразднён.** Он был зонтиком между целью и задачами — «задача, - которая не мерджится целиком». Зонтик теперь цель, а слишком крупный шаг просто - дробится на шаги помельче под той же целью, и промежуточному типу места не - осталось. Замер подтвердил: ноль употреблений на 97 записей двух живых - проектов. Встретился в чужом беклоге — это цель либо набор задач, и `check` - назовёт его неизвестным типом. +## Чего у задач больше нет + +**Тип `goal` и `ROADMAP.md` упразднены.** Цель была зонтиком над параллельными +направлениями: она нужна там, где список работ нельзя выстроить в один порядок, +и очередь идёт поперёк направлений. У проекта, который ведёт один человек, такого +не бывает — на стройке список линеен по зависимости, на доработке правки +независимы, — и зонтик не стоял ни над чем. + +Роадмап при этом отвечал на свой вопрос наполовину: «чего ещё не умеет» — это +«что осталось в беклоге», то есть пересказ второго индекса. Вторая половина, «что +уже умеет», живёт в двух домах и без него: нормативное поведение — в +`openspec/specs/`, а когда и в каком порядке оно появилось — в `git log` индекса +и коммитах задач. + +Вместе с целью ушли: секция `Готово` (её ответ дают спеки и история), теги +`goal:<слаг>` и `decomposed`, поле меты `Секция` (осталась `Категория`), раздел +`Завершение` и команды `list --goal`, `edit --goal`. Встретились в проекте — +`check` назовёт их поимённо, `check --fix` снимет теги, а запись типа `goal` +оставит человеку: во что она превращается — в задачу или в ничто, — машина не +решает. + +**Тип `[epic]` упразднён раньше и не вернулся.** Слишком крупный шаг дробится на +шаги помельче, стоящие в списке подряд. ## Тип записи -**Тип — единственная ось этого скилла, и он решает, что с записью можно делать.** +**Тип решает, что с задачей можно делать.** Вторая ось скилла — первая стадия. Перечень осей всего процесса и того, чего каждая **не** решает, — [shared/axes.md](../../shared/axes.md). Дом типа — **поле меты `Тип` первой строкой**; эмодзи в заголовке H1 от него производна, её ставит `add` и чинит `check --fix`. -| Тип | Обязательные разделы | Цель | В работу | Устав | -| --- | --- | --- | --- | --- | -| 🎯 `goal` | `Завершение` | — | нет | [task-goal.md](references/task-goal.md) | -| ✨ `feature` | `Затрагивает`, `Критерии приёмки` | **обязательна** | да | [task-feature.md](references/task-feature.md) | -| 🐞 `fix` | `Воспроизведение`, `Затрагивает`, `Критерии приёмки` | необязательна | да | [task-fix.md](references/task-fix.md) | -| 🧹 `chore` | `Затрагивает`, `Критерии приёмки` | нет | да | [task-chore.md](references/task-chore.md) | -| 🔬 `research` | `Вопрос`, `Куда ляжет ответ` | нет | да | [task-research.md](references/task-research.md) | +| Тип | Обязательные разделы | Устав | +| --- | --- | --- | +| ✨ `feature` | `Затрагивает`, `Критерии приёмки` | [task-feature.md](references/task-feature.md) | +| 🐞 `fix` | `Воспроизведение`, `Затрагивает`, `Критерии приёмки` | [task-fix.md](references/task-fix.md) | +| 🧹 `chore` | `Затрагивает`, `Критерии приёмки` | [task-chore.md](references/task-chore.md) | +| 🔬 `research` | `Вопрос`, `Куда ляжет ответ` | [task-research.md](references/task-research.md) | + +Берутся в работу все четыре: записи, которую нельзя взять, больше не существует. Сверх обязательных у любой задачи допустимы `Рамки` и `Вопросы`. Раздел не из схемы своего типа — **замечание, а не ошибка**: свой раздел законная вольность проекта, но `Воспроизведение` у `chore` почти всегда значит, что тип проставлен не тот, и сказать об этом стоит, не запрещая. -**Осей было две, и ортогональность у них была фальшивой.** Тип записи +**Прежних осей было две, и ортогональность у них была фальшивой.** Тип записи (`goal`/`idea`/`task`) и род работы (`kind:<род>` тегом) давали двенадцать клеток произведения, из которых законны были шесть: у цели род запрещён, у задачи обязателен, у идеи пуст. Плюс алгоритм работы крепится не к `task`, а к `fix` и @@ -301,7 +246,8 @@ stateDiagram-v2 изменения, а не по типу задачи: `chore` бывает миграцией схемы, `fix` — правкой публичного контракта. Правило «предписание процесса в теле задачи снимается» типом не отменяется, а подтверждается: он описывает работу, а не то, как её -проверять. +проверять. **Стадия проекта их тоже не выбирает**: изменение на стройке ничем не +проще того же изменения на доработке, и метку ему по-прежнему назначает разметка. **Одно исполнителю тип всё же говорит — каким сценарием работу вести, и то не один.** Скилл `av-dev:code-resolve` выбирает сценарий связкой из двух @@ -316,11 +262,10 @@ stateDiagram-v2 Три требования к тексту. Первое — про заголовок, два остальных про то, чтобы задачу можно было **оценить, не открывая код**. -**Заголовок отвечает на вопрос своего типа.** Вопросов три, поэтому и форм три: +**Заголовок отвечает на вопрос своего типа.** Вопросов два, поэтому и форм две: | Тип | Отвечает на | Пример | | --- | --- | --- | -| 🎯 `goal` | что приложение будет уметь | Соперником может быть компьютер | | ✨ `feature`, 🐞 `fix`, 🧹 `chore` | что нужно сделать | Печатать поле одним куском кода | | 🔬 `research` | о чём разведка | Подсказка следующего хода | @@ -333,10 +278,6 @@ stateDiagram-v2 исход знание, а что делать — ещё неизвестно, и заголовок-действие обещал бы решённость, которой нет. -Из этого же правила растёт разница индексов: роадмап — список возможностей, -беклог — список работ, и если заголовки перепутать формами, каждый из них -начинает читаться как другой. - `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 list --dir D [--stale] [--section S] [--type T] [--tag a,b] [--goal S] [--raw] [--index …] [--questions] -python3 $tk add --dir D --slug S --title T --type goal|feature|fix|chore|research [--section S] [--goal G] [--why «зачем»] [--tag a,b] -python3 $tk edit S --dir D [--title T] [--why «зачем»] [--type T] [--goal G] [--add-tag a,b] [--rm-tag c] +python3 $tk list --dir D [--stale] [--section S] [--type T] [--tag a,b] [--raw] [--questions] +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] [--add-tag a,b] [--rm-tag c] python3 $tk move S --dir D [--section S] [--reason R] [--after S | --first] # без --section — текущая секция python3 $tk close S --dir D --reason R # в REJECTED.md + удалить (ушла без реализации) python3 $tk close S --dir D --implemented # просто удалить (реализована и закоммичена) python3 $tk reopen S --dir D --reason R # вернуть закрытую: приёмка не сошлась python3 $tk ready S… --dir D # схема типа выполнена — можно брать в работу -python3 $tk init --dir D [--sections …] [--items …] [--backlog …] … -python3 $tk adopt scan --from … | apply --plan … # разовая адаптация, references/adopt.md +python3 $tk stage --dir D # показать стадию +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` в репозитории плагина: словарь общий @@ -422,35 +365,32 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап -Здесь это значит: код 1 приходит **только от `check`** — найден дрейф индексов и +Здесь это значит: код 1 приходит **только от `check`** — найден дрейф индекса и файлов, чинится `check --fix`, остаток разбирается руками. Код 3 — каталог не найден, конфиг битый или мимо диска: чинится путём или `.av-dev.toml` в корне. -Тип — английское ключевое слово `goal` / `feature` / `fix` / `chore` / -`research` (как и прочие токены команд), у `add` **обязательное**: без него -неизвестно, какой шаблон тела класть. Текст задачи при этом русский, а эмодзи в -заголовке ставит скрипт. +Тип — английское ключевое слово `feature` / `fix` / `chore` / `research` (как и +прочие токены команд), у `add` **обязательное**: без него неизвестно, какой +шаблон тела класть. Стадия — такое же слово, `build` / `support`, и у `init` она +обязательна по той же причине: без неё неизвестно, что писать в шапке беклога и +сколько заводить секций. Текст задачи при этом русский, а эмодзи в заголовке +ставит скрипт. -**Мутации правят файл и индексы заодно** — руками строку индекса или мету -не пиши, зови `add`/`edit`/`move`/`close`/`reopen`. Смена заголовка, «зачем», типа, -цели и **тегов** — это `edit`: он держит H1 (вместе с эмодзи), мету и индекс +**Мутации правят файл и индекс заодно** — руками строку индекса или мету +не пиши, зови `add`/`edit`/`move`/`close`/`reopen`. Смена заголовка, «зачем», типа +и **тегов** — это `edit`: он держит H1 (вместе с эмодзи), мету и индекс согласованными. Снятие тега — `--rm-tag` (после ответа на вопрос снимается -`question`), смена цели — `--goal`, типа — `--type`; оба заменяют прежнее -значение, а не добавляют второе. +`question`), смена типа — `--type`; оба заменяют прежнее значение, а не +добавляют второе. -**Переезд между индексами — следствие смены типа, а не отдельная команда.** -`edit --type goal --section <часть роадмапа>` переносит строку из -`BACKLOG.md` в `ROADMAP.md` (и обратно — задачным типом плюс -`--section <категория беклога>`); -`move` двигает только внутри одного индекса и пишет причину. `--section` у -`edit` работает **только** при таком переезде — иначе он отсылает к `move`, -потому что смена секции без причины и есть тот дрейф, который потом никто не -объяснит. +**Секцию меняет только `move`, и он пишет причину**: смена полки без причины и +есть тот дрейф, который потом никто не объяснит. -**`move --after <слаг>` и `move --first` — это и есть расстановка приоритета.** +**`move --after <слаг>` и `move --first` — это и есть расстановка порядка.** Порядок строк в секции значим (правило 4), и двигают его только этой командой: руками поправленная строка не оставляет причины, а причина здесь и есть половина -решения. +решения. Что именно этот порядок значит, говорит стадия: на стройке `--after` +называет зависимость, на доработке — приоритет. Тело задачи скрипт не трогает: `add` кладёт заголовок, мета-блок и шаблон с подсказками, тело дописываешь @@ -461,18 +401,23 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап правки**, даже если правил мутациями: дрейф мог накопиться раньше. Накопившееся чини `check --fix` — он детерминированно правит то, где истина однозначна (тип в своё поле, эмодзи заголовка, имя поля места, секция, заголовок, дубли, «зачем» из -индекса в файл, старая форма меты, пометка `decomposed` у цели с задачами, сырьё -в конец категории), а неоднозначное (задача сразу в двух индексах, нечего -восстанавливать, **тип, которого неоткуда взять**) печатает отдельной пометкой +индекса в файл, старая форма меты, снятые теги упразднённых целей, сырьё +в конец секции), а неоднозначное (нечего восстанавливать, **тип, которого +неоткуда взять**, запись типа `goal`) печатает отдельной пометкой `НЕОДНОЗНАЧНО` — это тебе, и это идёт строкой доклада. **Ссылка на исчезнувший файл в пометку не попадает:** `--fix` её просто не трогает, и она остаётся `ОШИБКА` обычного `check` — то есть видна, но в докладе её надо назвать отдельно. `--fix` правит **и файлы** — там, где источник ровно один и выбирать не из чего: тип переезжает из прежнего дома (тег `kind:`, префикс `[goal]`/`[idea]`) в поле -меты, заголовок получает эмодзи, поле места — имя по типу, «зачем», оставшееся -только в индексе, переезжает в мету, цель с задачами получает `decomposed`. -Каждый случай печатается поимённо. +меты, заголовок получает эмодзи, поле места зовётся «Категория», «зачем», +оставшееся только в индексе, переезжает в мету, теги `goal:` и `decomposed` +снимаются. Каждый случай печатается поимённо. + +**Ни стадию, ни состав секций `--fix` не трогает.** Стадию он подставить не +может — это решение человека; секции стройки не сливает — в каком порядке пойдут +строки слитых полок, знает тоже только человек, а порядок здесь и есть +содержание. **Тип, который не выводится ниоткуда, `--fix` не угадывает.** `feature` от `chore` машина не отличает, и подставленное наугад значение врало бы ровно там, @@ -483,7 +428,7 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап работу — там, где по ней принимают решение; `check` поимённо о ней не говорит, а считает: строка здоровья **«схема типа не выполнена: 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); там же тест «готова к взятию». Схема и алгоритм каждого типа — по файлу на тип: -[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) · [research](references/task-research.md). @@ -530,9 +475,13 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап ### Завести запись из диалога -1. **Фильтр.** Делаем прямо сейчас — не заводим. Не пожалеем о потере — не - заводим. Родилось три кандидата — покажи их и спроси, какие заводить: молча - заведённая пачка и есть тот самый отказ из правила 1. +0. **Посмотри стадию** — `stage`. От неё зависят шаг 1 и место новой строки: на + доработке беклог пополняют по одной и с фильтром, на стройке пишут планом. +1. **Фильтр — на доработке.** Делаем прямо сейчас — не заводим. Не пожалеем о + потере — не заводим. Родилось три кандидата — покажи их и спроси, какие + заводить: молча заведённая пачка и есть тот самый отказ из правила 1. + **На стройке фильтра нет**: план пишется вперёд целиком, и «этого мы сейчас + не делаем» — не довод против шага, а описание всякого шага, кроме первого. 2. **Дедуп.** `list` плюс поиск по слагам, полю «зачем» и телам (`grep -ril`), **включая `REJECTED.md`**. Нашлось среди живых — **дописываем в существующий файл**, а не заводим соседний. Нашлось в `REJECTED.md` — покажи пользователю @@ -541,7 +490,6 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап переоценки. 3. **Тип** — `--type` обязателен, и он же первое содержательное решение: - - возможность приложения, а не шаг к ней → `goal`; - снаружи появляется то, чего не было → `feature`; - поведение расходится с заявленным и **воспроизводится** → `fix` (не воспроизводится → `research`); @@ -550,12 +498,12 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап Не подходит ни один — в записи их два, разбирай. Не проходит тест готовности (см. task-format) — это **сырьё**: `--type research`, раздел «Вопрос» пока - пуст, место в конце категории. Не делается одним заходом — это не эпик, а - несколько задач под одной целью: дроби сразу. -4. **Цель — если тип её требует.** У `feature` должен быть `--goal <слаг>`: - новая возможность и есть содержание цели. Подходящей нет — либо она - заводится (`--type goal`), либо перед тобой не `feature`. У `fix`, `chore` и - `research` цели может не быть вовсе, и придумывать её не надо. + пуст, место в конце секции. Не делается одним заходом — дроби на шаги + помельче и ставь их в списке подряд. +4. **Место в списке.** `add` кладёт строку в конец секции всегда. На стройке это + почти наверняка не то место: порядок там зависимость, и новый шаг чаще всего + встаёт в середину — `move <слаг> --after <слаг>`. На доработке конец списка + законен: место в очереди назначает груминг, а не заведение. 5. `add …`, затем допиши тело редактором **по схеме своего типа** — шаблон её уже разложил, устав типа объясняет каждый раздел. «Зачем» отвечает «зачем нужна эта задача» — состояние, остаток, боль, — а не пересказывает первый @@ -569,13 +517,13 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап пять файлов из одной мысли, а сорок файлов из сорока сырых находок. Защита та же, что в самом ревью: кластеризация по причине, дедуп против живых и `REJECTED.md`, находка без свидетельства → сырьё (`research`), а не задача, и карта кластеров -пользователю до создания файлов. Порядок, отображение серьёзности и привязка к -целям — [references/from-review.md](references/from-review.md). +пользователю до создания файлов. Порядок и отображение серьёзности — +[references/from-review.md](references/from-review.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-wording` | залог и отглагольные, оценка без факта, стоп-слова, англицизмы, жаргон, неизвестный термин, транслит в слаге | `items/` и строки индексов; документы проекта — только как словарь | +| `task-form` | заголовок по типу, «зачем» вместо пересказа, границы вместо замысла, годность оракулов, предписание процесса | только `items/` | +| `task-wording` | залог и отглагольные, оценка без факта, стоп-слова, англицизмы, жаргон, неизвестный термин, транслит в слаге | `items/` и строки индекса; документы проекта — только как словарь | Разделены они не по охвату, а **по глубине**. Язык проверяется по словам и -фразам, поштучно; форма записи требует понять, что задача делает, и открыть -цель, на которую она ссылается. Слитый проход одну половину делает дорогой, а -вторую — поверхностной. +фразам, поштучно; форма записи требует понять, что задача делает. Слитый проход +одну половину делает дорогой, а вторую — поверхностной. Модель у обоих одна, `sonnet`, и это не отменяет разреза. Оба судят по **записанному правилу** — семь пунктов формы против правил языка, — а их находка @@ -690,18 +637,20 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап полагаться на него скилл не должен: молча найденный чужой каталог это дрейф. - **Версия и настройки живут в `.av-dev.toml` в корне репозитория** — версия ключом `version`, настройки каталога секцией `[tasks]`: `dir` — где каталог - лежит, плюс **имена** файлов и заголовков, и последние только если отличаются - от умолчания. Неизвестный ключ в секции — код 3 на любой команде, так что - лишнее слово останавливает работу с задачами целиком. + лежит, `stage` — стадия проекта, плюс **имена** файлов и заголовков, и + последние только если отличаются от умолчания. Неизвестный ключ в секции — код + 3 на любой команде, так что лишнее слово останавливает работу с задачами + целиком. Дом в корне, а не внутри каталога задач, по двум причинам: настройка, лежащая внутри настраиваемого каталога, не смогла бы сказать, **где он**; и версия одна на весь плагин, а корень есть и у проекта без `docs/`. Прежние `<каталог задач>/.tasks.json` и `docs/.docs.json` не читаются — увидев их, скрипт говорит «прежняя раскладка» и зовёт `upgrade`. -- **Секции беклога** берутся из заголовков `##` индекса как есть; их количество - и названия — дело проекта (умолчание `Ядро` / `Инфра`). **В конфиге их нет** — - второй список разошёлся бы с заголовками молча. +- **Секции беклога** берутся из заголовков `##` индекса как есть; их названия — + дело проекта (умолчание `План` на стройке, `Ядро` / `Инфра` на доработке), а + количество ограничено стадией: на стройке секция одна. **В конфиге секций + нет** — второй список разошёлся бы с заголовками молча. ### Вызов из другого плагина @@ -738,8 +687,10 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап - **Развилки — пользователю.** Через `AskUserQuestion`, с уже сформулированным предварительным суждением (**рекомендация — первым вариантом**). Что выкинуть, - под какую цель отнести, какая рамка разведки верна — решение пользователя. Слаг, - формулировка, порядок строк в индексе — механика, делаем сами. + какая рамка разведки верна, пора ли менять стадию — решение пользователя. Слаг + и формулировка — механика, делаем сами. **Порядок строк механикой не + считается** ни на одной стадии: на стройке он зависимость, на доработке + приоритет, и оба называет человек. - **Не больше трёх вопросов за раз.** Пачка длиннее трёх тяжела для ответа; решений больше — веди **несколько итераций** диалога по ≤3, а не один перегруженный запрос. Между итерациями применяй уже решённое. diff --git a/av-dev/skills/task-track/references/adopt.md b/av-dev/skills/task-track/references/adopt.md index 864def6..999f46c 100644 --- a/av-dev/skills/task-track/references/adopt.md +++ b/av-dev/skills/task-track/references/adopt.md @@ -1,7 +1,7 @@ # Адаптация каталога задач Проект, где задачи уже как-то ведутся, и из имеющегося материала **выводится** -заполненный каталог задач: цели, задачи, кладбище, индексы. Операция разовая — +заполненный каталог задач: задачи, кладбище, индекс. Операция разовая — после неё проект живёт скиллами `task-track` и `task-groom`. **Это часть приведения проекта к канону.** Раскладку `docs/` целиком ведёт скилл @@ -12,12 +12,12 @@ Вход какой угодно: старая раскладка `av-dev-backlog` (индекс `README.md`, кладбище `CLOSED.md`, приоритеты секциями, транслитные слаги, файлы рядом с индексом), `TODO.md`, россыпь заметок, раздел «планы» в `README.md`, список -шагов роадмапа проекта. +шагов плана проекта. ## Три правила, из которых всё следует -1. **Сперва карта, потом файлы.** Человеку показывается, что найдено, как - разложилось по целям и **что не разложилось**, — и только после подтверждения +1. **Сперва карта, потом файлы.** Человеку показывается, что найдено, в каком + порядке разложилось и **что не разложилось**, — и только после подтверждения пишется хоть один файл. Это то же правило, что у интейка находок ревью: массовое заведение записей без подтверждения — самый дорогой отказ, потому что разгребает его потом переоценка. @@ -38,7 +38,7 @@ tk="$CLAUDE_PLUGIN_ROOT/skills/task-track/scripts/tasks.py" 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 \ --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` в `tie-break-equal-completeness` может только тот, кто понимает смысл. `scan` честно говорит: проверить надо **все** слаги, признаки транслита — эвристика; -- **цели.** Шаги роадмапа — готовые цели в **`Запланировано`** (очередь и - обоснование у них уже есть); тематические скопления задач — цели в - **`Направления`** («прочность слияния», - «журнал и пересборка»). Предлагаешь ты, назначает человек; +- **стадия.** `--stage` называет, чем этот беклог будет: планом стройки или + очередью правок. Машине это не выводится — она видит список пунктов, а не то, + построено приложение или нет; +- **порядок.** Нумерованные шаги источника `scan` сохраняет по номерам, прочие + ставит следом. Дальше порядок — твоё суждение и подтверждение человека: на + стройке это зависимость, на доработке важность; - **что вообще не задача.** Обоснование порядка шагов, абзац прозой, заголовок раздела — это не пункты беклога, и они уходят в «не разложилось» с причиной. ## Порядок -1. **Осмотрись.** Где лежат задачи, роадмап, заметки. Каталог задач по канону — - всегда `tasks`. Секции беклога (`--sections`) — по умолчанию - `Ядро,Инфра`; если у проекта деление другое по существу, оно называется - здесь, а не подгоняется под умолчание, и становится **заголовками `##` - индекса** — их единственным домом. В `.av-dev.toml` секции не пишутся: там - версия формата и имена частей, а второй список секций разошёлся бы с - заголовками молча. +1. **Осмотрись и назови стадию.** Где лежат задачи, план, заметки; построено + приложение или строится. Каталог задач по канону — всегда `tasks`. Секции + беклога (`--sections`) — на стройке ровно одна (умолчание `План`), на + доработке сколько нужно (умолчание `Ядро,Инфра`); если у проекта деление + другое по существу, оно называется здесь, а не подгоняется под умолчание, и + становится **заголовками `##` индекса** — их единственным домом. В + `.av-dev.toml` секции не пишутся: там версия, стадия и имена частей, а второй + список секций разошёлся бы с заголовками молча. 2. **`adopt scan`** по всем источникам разом. Один прогон, одна карта: два прохода дадут два несогласованных состояния. -3. **Заполни карту**: `slug` (английский), `section`, `goal` у каждой записи; - список `goals` — из шагов роадмапа и из тем. Закрытый шаг целью не - заводится. Пустой `goal` законен у `fix`, `chore` и `research` — они служат - работоспособности, а не направлению; у `feature` цель обязательна. +3. **Заполни карту**: `slug` (английский), `type` и `section` у каждой записи, и + **порядок `items`** — он уедет в индекс как есть. Пункт, помеченный + закрытым, не переносится вовсе. 4. **Покажи человеку карту** через `AskUserQuestion`, ≤3 вопроса за итерацию, - рекомендация первым вариантом. Показывается: сколько записей, предлагаемые - цели (порядок и темы) с обоснованием, спорные отнесения, список «не - разложилось». Массовые механические решения (слаги, порядок строк) не - выносятся — это механика. + рекомендация первым вариантом. Показывается: сколько записей, предлагаемый + порядок с обоснованием, спорные отнесения, список «не разложилось». Слаги не + выносятся — это механика; **порядок выносится всегда**, потому что механикой + он не является ни на одной стадии. 5. **`adopt apply`.** `--refs` перечисляет **всё**, где могут стоять ссылки на слаги: документация, архив изменений, `CLAUDE.md`, `README.md`. Скрипт посчитает и покажет, сколько ссылок поправлено и по каким слагам. 6. **`tasks.py check`** и доклад. `apply` отказывается писать поверх живого каталога и проверяет карту целиком -**до** первой записи: неверная секция, дубль слага, цель, которой нет в карте — -всё это отказ до того, как на диске появился хотя бы один файл. +**до** первой записи: неверная секция, дубль слага, неназванный тип, две секции +при стадии `build` — всё это отказ до того, как на диске появился хотя бы один +файл. ## Переходное состояние — объявляется, а не заминается Сразу после адаптации задачи в большинстве своём **не готовы к взятию**: у них -нет критериев приёмки, а у части может не быть цели. Это нормально, но обязано -быть названо, иначе следующий агент примет пустой беклог за поломку. +нет критериев приёмки. Это нормально, но обязано быть названо, иначе следующий +агент примет пустой беклог за поломку. -`apply` печатает состояние по факту: сколько задач без цели (это **ошибки** -`check`) и сколько не собрало разделы своего типа (для `check` это не ошибка, а -строка здоровья, но `ready` такую задачу не пропустит). Закрывается это -**порциями груминга** — скилл -`groom`, 5–8 задач за порцию: проставить цели, превратить «готово, когда» в -критерии с оракулами, вынуть вопросы из прозы в раздел «Вопросы». Там же -беклогу впервые назначается **порядок**: после адаптации его нет вовсе, а -очередь и есть то, ради чего каталог заводят. +`apply` печатает состояние по факту: сколько задач не собрало разделы своего типа +(для `check` это не ошибка, а строка здоровья, но `ready` такую задачу не +пропустит). Закрывается это **порциями груминга** — скилл `groom`, 5–8 задач за +порцию: превратить «готово, когда» в критерии с оракулами, вынуть вопросы из +прозы в раздел «Вопросы». + +**Порядок строк проверяется глазами отдельно.** На стройке он выведен из +нумерации источника, и там, где её не было, он случаен. На доработке машина +важности не знает вовсе — очередь расставляется первым же грумингом. Готовность к первой задаче — не «`check` зелёный», а «`ready` пропускает хотя бы верхние строки очереди». @@ -113,18 +117,20 @@ python3 $tk adopt apply --plan tasks-adopt-plan.json \ дело человека, удалять чужое молча нельзя. В доклад идёт готовая команда. - **Не переписывает подписи ссылок.** `[docs/backlog](tasks/BACKLOG.md)` — цель поправлена, текст остался; это правится глазами, и таких мест немного. -- **Не сочиняет критерии приёмки и не придумывает цели**, которых в материале - нет. Придуманная цель хуже отсутствующей: под неё заведут задачи. +- **Не сочиняет критерии приёмки** и не додумывает пользы, которой в материале + нет. +- **Не выводит стадию.** Список пунктов одинаково выглядит и планом стройки, и + очередью правок; отвечает `--stage`, а называет его человек. - **Не трогает историю.** В коммитах старые слаги остаются, и это нормально. ## Доклад - Источники и что в каждом распознано (раскладка, индекс, кладбище, секции). -- Сколько записей перенесено, сколько целей заведено (порядок / темы) и откуда - каждая выведена. +- Стадия и сколько записей перенесено; откуда взялся порядок (нумерация + источника или суждение). - **Переименования**: сколько слагов, сколько ссылок поправлено и в скольких файлах — числом, а не «поправлены ссылки». - **Не разложилось**: поимённо, с причиной. -- Переходное состояние: сколько задач без цели, сколько без критериев, чем и за - сколько порций закрывается. +- Переходное состояние: сколько задач без критериев, чем и за сколько порций + закрывается. - `tasks.py check` — результат строкой. diff --git a/av-dev/skills/task-track/references/from-review.md b/av-dev/skills/task-track/references/from-review.md index 46758b4..0880fb5 100644 --- a/av-dev/skills/task-track/references/from-review.md +++ b/av-dev/skills/task-track/references/from-review.md @@ -50,17 +50,12 @@ заведённое и уже выкинутое. Нашлось среди живых — дописываем находку в существующий файл. Нашлось в `REJECTED.md` — это сигнал: причина отказа могла устареть, выноси пользователю, а не заводи молча заново. -4. **Разложи по целям — там, где цель нужна.** Большинство находок ревью это - `fix` и `chore`, и **цель им не требуется**: они служат работоспособности, а - не направлению. Придуманная им цель — - ровно то враньё, от которого спасает тип. - - Цель обязательна у находки, которая оказалась **новой возможностью** - (`feature`): нашлось поведение, которого никто не заказывал, и его надо - либо заказать целью, либо убрать. Подходящей цели нет — заведи её - (`add --type goal --section Направления`) в том же проходе. +4. **Проставь типы.** Большинство находок ревью это `fix` и `chore`. Находка, + оказавшаяся **новой возможностью** (`feature`), — отдельный случай: нашлось + поведение, которого никто не заказывал, и решение тут не «завести задачу», а + «заказать или убрать». Выноси такую пользователю отдельно от прочих. 5. **Покажи карту до создания файлов.** Кластер → задача / сырьё / строка в - пакетный файл / уже заведено / отброшено, и под какую цель — пачкой через + пакетный файл / уже заведено / отброшено — пачкой через `AskUserQuestion`. Это тот же барьер, что и «три кандидата» в интейке из диалога: массовое заведение файлов без подтверждения — ровно тот отказ, ради которого интейк из ревью и выделен. Дешёвая мелочь по явному согласию может @@ -88,8 +83,8 @@ [скилле груминга](../../task-groom/SKILL.md#приоритет-как-его-расставляют), и серьёзность попадает ровно в один из них. -- **тяжёлая находка со свидетельством о сломанном сейчас** → задача под ту цель, - которой она угрожает, и **первой строкой секции**: `move <слаг> --first +- **тяжёлая находка со свидетельством о сломанном сейчас** → задача + **первой строкой секции**: `move <слаг> --first --reason «сломано сейчас: …»`. Это довод «что сломано сейчас» из перечня груминга — единственный, который не требует сравнения с соседями по очереди, потому что сломанное дорожает само. Позицию всё равно назначает человек, и @@ -124,7 +119,7 @@ ## Доклад - Источник (какое ревью/аудит, сколько находок на входе). -- Свёрнуто в задачи: N кластеров из M находок, со слагами, целями и тегом партии. +- Свёрнуто в задачи: N кластеров из M находок, со слагами и тегом партии. - Что не заведено и почему: починено инлайн, уже заведено, стало сырьём, ушло в `REJECTED.md`. - Поимённая сверка: находок на входе N, исход есть у N. diff --git a/av-dev/skills/task-track/references/split.md b/av-dev/skills/task-track/references/split.md index 3ecaa94..c28065d 100644 --- a/av-dev/skills/task-track/references/split.md +++ b/av-dev/skills/task-track/references/split.md @@ -8,14 +8,19 @@ Задачу можно дробить, только если части удовлетворяют **обоим** условиям: -1. **Мерджатся независимо.** Часть Б не требует, чтобы часть А была уже влита. - Есть порядок «сперва А, потом Б, иначе не собрать» → это не декомпозиция, а - план реализации: шаги остаются **внутри одного файла**. +1. **Каждая мерджится сама по себе.** Часть, после которой дерево не собирается + или поведение сломано до прихода соседней, — не часть, а половина. 2. **Каждая — самостоятельный шаг.** Часть, осмысленная только в комплекте с - другой, — не задача. Проверяй тестом «готова к взятию» (task-format): какую - строку «Завершения» цели двигает **именно эта часть** и какие у неё - собственные критерии приёмки. У операционных частей (`fix`, `chore`, - `research`) цели может не быть — тогда достаточно собственных критериев. + другой, — не задача. Проверяй тестом «готова к взятию» (task-format): свои + критерии приёмки у неё есть или нет. + +**Порядок между частями законен на стройке и подозрителен на доработке**, и это +единственное, что стадия здесь меняет. Беклог стройки **весь** состоит из +упорядоченных зависимостью шагов: «сперва А, потом Б» — не повод не дробить, а +описание того, как этот список устроен, и части просто встают подряд. На +доработке правки независимы, и обнаруженный порядок «иначе не собрать» чаще +всего значит, что перед тобой не декомпозиция, а план реализации: шаги остаются +**внутри одного файла**. Не проходит хотя бы одно — **не дроби**. Ложная декомпозиция плодит файлы, которые нельзя взять поодиночке, и переоценка потом склеивает их обратно. @@ -45,37 +50,29 @@ гигиена полей снимает. Шов пользуется меткой как **признаком**, что в задаче две разнородные работы; решение о метке остаётся за конвейером. -**Цель наследуется.** Все части несут `goal:` родителя: декомпозиция не меняет -того, чему работа служит. Если у части цель другая — это признак, что дробили не -по той границе, либо что часть вообще из другой работы. - ## Что делать с родителем После разделения родитель **не остаётся** третьей висящей строкой: -- части полностью замещают его → `close --reason "разложена на a, b"`. - `REJECTED.md` здесь — не «выкинули», а именно тот след, что переживает запись: - через квартал вопрос «куда делась задача X» отвечается строкой со ссылками на - наследников, а не археологией git; -- родитель осмыслен как **возможность**, а не как шаг → это цель, и **строка - переезжает**: `edit --type goal --section <часть роадмапа>` снимает её с - `BACKLOG.md` и вставляет в `ROADMAP.md`. Файл в `items/` при этом не двигается — - он и есть запись. Части получают `--goal <слаг родителя>`, а закрывать родителя - нечем и незачем: он не выкинут, он стал целью. +части полностью замещают его → `close --reason "разложена на a, b"`. +`REJECTED.md` здесь — не «выкинули», а именно тот след, что переживает запись: +через квартал вопрос «куда делась задача X» отвечается строкой со ссылками на +наследников, а не археологией git. -**Промежуточного зонтика между целью и задачей нет.** Тип `epic` упразднён: -роль зонтика играет цель, а слишком крупный шаг дробится на шаги помельче под -той же целью. Если частям нужен общий заголовок — значит у них общая -возможность, и её надо назвать целью, а не заводить временный тип. +**Зонтика над частями нет никакого.** Тип `epic` упразднён, цель, игравшая его +роль после него, — тоже. Если частям нужен общий заголовок, у них общее место в +списке: они встают подряд, и соседство и есть тот ответ, ради которого заводили +зонтик. ## Когда декомпозиция случается посреди работы Задача, которая **оказалась крупнее задачи**, распознаётся до того, как под неё заведено предложение об изменении: иначе его придётся выбрасывать. Она выходит из работы на декомпозицию, а её строка возвращается в беклог с причиной -(`move … --reason "крупнее задачи"`). Части заводятся сразу под той же целью, и -**место в очереди им назначает человек**: машина поставит их в конец секции, а -крупная задача редко распадается на что-то менее срочное, чем была сама. +(`move … --reason "крупнее задачи"`). **Место в списке частям назначает +человек**: машина поставит их в конец секции, а на стройке место наследуется от +родителя (`move --after`), да и на доработке крупная задача редко распадается на +что-то менее срочное, чем была сама. ## Мозговой штурм сырья @@ -96,9 +93,9 @@ Applicative-штурм («перечисли задачи, следующие и applicative. 2. **Вынеси формы пользователю** через `AskUserQuestion` с компромиссами. Рамку выбирает он: это продуктовое решение, не механика. -3. **Назови цель.** Выбранная форма служит цели — существующей или новой. Идея, - для которой цель не находится, скорее всего уезжает в `REJECTED.md`, а не - заводится задачей. +3. **Назови пользу.** Выбранная форма отвечает на «что станет наблюдаемо иначе». + Идея, для которой такого ответа не находится, скорее всего уезжает в + `REJECTED.md`, а не заводится задачей. 4. **Только выбранную форму** дроби по тесту декомпозиции выше и проставь критерии приёмки: без них наследники останутся идеями под другим именем. @@ -109,8 +106,8 @@ Applicative-штурм («перечисли задачи, следующие и ## Доклад - Идея/задача на входе, выбранная рамка (для штурма), задачи-наследники со - слагами, целями и секциями. -- Судьба родителя: удалён / стал целью / выкинут с причиной. + слагами, секциями и местом в списке. +- Судьба родителя: удалён / выкинут с причиной. - `tasks.py check` после правок. - Границы покрытия: какие постановки рассмотрены и какие сознательно отброшены — чтобы штурм не пришлось повторять с нуля. diff --git a/av-dev/skills/task-track/references/task-chore.md b/av-dev/skills/task-track/references/task-chore.md index 601a729..3566ec1 100644 --- a/av-dev/skills/task-track/references/task-chore.md +++ b/av-dev/skills/task-track/references/task-chore.md @@ -14,7 +14,6 @@ | Обязательные разделы | `Затрагивает`, `Критерии приёмки` | | Допустимые сверх того | `Рамки`, `Вопросы` | | Поле места | **Категория** — полка домена беклога | -| Цель (`goal:<слаг>`) | нет: цель — это возможность, а здесь её не появляется | | Индекс | `BACKLOG.md` | | Берётся в работу | да | @@ -43,7 +42,7 @@ ## Алгоритм 1. **Проверить, что поведение не меняется.** Меняется — это `feature` или `fix`, - и у неё другие требования (цель, воспроизведение). + и у последнего другие требования (воспроизведение). 2. **Назвать, что перестанет мешать** — одной фразой, адресуясь разработчику. «Прибраться в модуле X» — не ответ: непонятно, что изменится. 3. **Назвать границы** в `Затрагивает`. У обслуживания они часто не в коде: @@ -55,9 +54,6 @@ 5. **Проверить, что это не «заодно».** Обслуживание любит склеиваться в пачку («обновить зависимости и переписать сборку и убрать мёртвый код»). Не мерджится порознь — это несколько задач ([split.md](split.md)). -6. **Цель не проставлять.** `chore` служит работоспособности, а не направлению. - Работа по сопровождению проекта при этом видна в роадмапе — секцией - `Сопровождение`, но целью не становится. ## Кто такую задачу решает diff --git a/av-dev/skills/task-track/references/task-feature.md b/av-dev/skills/task-track/references/task-feature.md index d00a1ee..7dd7c3c 100644 --- a/av-dev/skills/task-track/references/task-feature.md +++ b/av-dev/skills/task-track/references/task-feature.md @@ -15,18 +15,13 @@ | Обязательные разделы | `Затрагивает`, `Критерии приёмки` | | Допустимые сверх того | `Рамки`, `Вопросы` | | Поле места | **Категория** — полка домена беклога | -| Цель (`goal:<слаг>`) | **обязательна** | | Индекс | `BACKLOG.md` | | Берётся в работу | да | -**Цель обязательна, и это единственный тип, у которого так.** Новая возможность -и есть содержание цели: подходящей нет — либо она заводится, либо перед тобой не -`feature`. `ready` без цели откажет. - ## Алгоритм -1. **Найти цель или завести её.** Задача без цели, названная функцией, — самый - частый способ пронести в беклог работу, которой никто не заказывал. +1. **Проверить, что возможность и правда новая.** Поведение расходится с уже + заявленным — это `fix`, а не `feature`, и требования у него другие. 2. **Назвать границы** в разделе `Затрагивает`: эндпоинт или команда, таблица и миграция, формат на диске, публичный тип пакета, внешний сервис. Названы **границы, а не замысел**: «переписать хранилище на новый драйвер» — замысел, @@ -35,13 +30,12 @@ 3. **Написать критерии приёмки** — 2–5 проверяемых утверждений списком, у каждого назван оракул. Не «работает корректно», а «повторный прогон даёт тот же отпечаток — оракул: команда сверки». -4. **Сказать, какую строку «Завершения» цели задача двигает.** Одной строкой в - теле. Это защита от задачи «отрефакторить X»: она проваливается не потому, - что невидима снаружи, а потому, что не находит строки, к которой относится. +4. **Поставить её на место в списке.** На стройке место называет зависимость: + `move <слаг> --after <шаг, без которого нельзя>`. На доработке место в + очереди назначает груминг, и конец списка законен. 5. **Проверить, что задача одна.** Отвечается всё, но задача не делается одним - заходом и не мерджится целиком — это несколько задач под одной целью, дроби - сразу ([split.md](split.md)). Промежуточного зонтика между целью и задачей - нет. + заходом и не мерджится целиком — это несколько задач, дроби сразу + ([split.md](split.md)) и ставь их в списке подряд. 6. **Реализация** — дело конвейера проекта, не этого скилла. Закрывается `close <слаг> --implemented`: файл и строка удаляются, суть переезжает в `openspec/specs/` и документацию. @@ -49,8 +43,8 @@ ## Что видит машина, а что человек Схему типа судит `ready` на входе в работу: **наличие непустого** раздела -`Затрагивает`, **число** критериев (меньше двух — отказ, больше пяти — -замечание) и цель. Наличие оракула проверяется **эвристикой** — словом «оракул» +`Затрагивает` и **число** критериев (меньше двух — отказ, больше пяти — +замечание). Наличие оракула проверяется **эвристикой** — словом «оракул» в пункте. `check` этого поимённо не говорит, а считает строкой здоровья (`SKILL.md`, «Что механизировано, а что нет»). diff --git a/av-dev/skills/task-track/references/task-fix.md b/av-dev/skills/task-track/references/task-fix.md index 3a9871b..6aef83a 100644 --- a/av-dev/skills/task-track/references/task-fix.md +++ b/av-dev/skills/task-track/references/task-fix.md @@ -16,7 +16,6 @@ | Обязательные разделы | **`Воспроизведение`**, `Затрагивает`, `Критерии приёмки` | | Допустимые сверх того | `Рамки`, `Вопросы` | | Поле места | **Категория** — полка домена беклога | -| Цель (`goal:<слаг>`) | необязательна | | Индекс | `BACKLOG.md` | | Берётся в работу | да | @@ -54,9 +53,7 @@ почти всегда есть парный критерий: **прежнее поведение не сломалось** («ввод `а1` принимается по-прежнему»). Без него починка чинит одно и ломает соседнее. -6. **Цель не выдумывать.** `fix` служит работоспособности, а не направлению. - Придуманная цель — то же враньё, от которого спасает тип. -7. **Записать дефект в журнал** `docs/review.md` с пометкой «проскочил / пойман +6. **Записать дефект в журнал** `docs/review.md` с пометкой «проскочил / пойман ревью». Проскочившие — проверочный набор для калибровки конвейера; пойманные с оракулом — лучшая опора для прохода ревью: проектные, воспроизводимые, однажды оказавшиеся правдой. diff --git a/av-dev/skills/task-track/references/task-format.md b/av-dev/skills/task-track/references/task-format.md index 44e9e47..32e9586 100644 --- a/av-dev/skills/task-track/references/task-format.md +++ b/av-dev/skills/task-track/references/task-format.md @@ -1,4 +1,4 @@ -# Формат записей и индексов +# Формат записей и индекса Заголовок, мета-блок и строку индекса ставит `tasks.py add` — руками их не пишут. Этот файл описывает **общую форму** любой записи и то, что проверяет @@ -9,7 +9,6 @@ | Тип | Файл | Одной строкой | | --- | --- | --- | -| 🎯 `goal` | [task-goal.md](task-goal.md) | возможность приложения | | ✨ `feature` | [task-feature.md](task-feature.md) | снаружи появляется то, чего не было | | 🐞 `fix` | [task-fix.md](task-fix.md) | поведение расходится с заявленным | | 🧹 `chore` | [task-chore.md](task-chore.md) | обслуживание, поведение не меняется | @@ -25,7 +24,6 @@ - **Тип:** fix - **Категория:** Ядро — вернулась из работы: остаток писал нерешённое в журнал - **Зачем:** ввод «а1б2» ходит в a1 — игрок не видит, что ошибся, и винит игру -- **Теги:** goal:merge-robustness Разбор хода читает первые два символа и молча выбрасывает остаток строки. @@ -55,8 +53,8 @@ **эмодзи типа**, и она **производна**: её ставит `add` и чинит `check --fix` по полю меты. Второго дома у типа нет — эмодзи это его отображение, как строка индекса это отображение файла. -- **Форма заголовка — по типу.** Цель отвечает на «что приложение будет уметь»; - `feature`, `fix` и `chore` — на «что нужно сделать», глаголом в неопределённой +- **Форма заголовка — по типу.** `feature`, `fix` и `chore` отвечают на «что + нужно сделать», глаголом в неопределённой форме, перед ним допускается «не»; `research` называет предмет разведки и формы действия **не несёт намеренно**. Почему так — SKILL.md, «Как написана задача». `check` считает заголовки не в форме действия и печатает число в @@ -67,9 +65,8 @@ теги необязательны. Нераспознанные поля сохраняются: скрипт правит свои и не трогает чужие. - **Тип — первым полем.** Он решает, что у записи вообще может быть: какие - разделы обязательны, нужна ли цель, берётся ли она в работу, — и читается - раньше всего остального. Словарь **закрыт**: `goal` | `feature` | `fix` | - `chore` | `research`. Не подходит ни один — это сигнал, что в записи их два и + разделы обязательны и берётся ли она в работу, — и читается раньше всего + остального. Словарь **закрыт**: `feature` | `fix` | `chore` | `research`. Не подходит ни один — это сигнал, что в записи их два и её надо разделить. - **«Зачем» отвечает на «зачем нужна эта задача»** — состояние, остаток, боль. Не пересказ задачи: пересказ уже есть по ссылке. Живёт здесь, а не только в @@ -86,19 +83,16 @@ Тело — не план реализации и не спецификация: принятое и реализованное переезжает в документацию проекта, а файл задачи удаляется. -### Поле места: «Категория» и «Секция» +### Поле места: «Категория» -Поле называет, **где числится строка**, и имя у него **зависит от типа**: +Поле называет **секцию беклога, в которой числится строка** — полку домена +(`Ядро`, `Инфра`, …), куда задачу положили и куда вернут, если она уйдёт в работу +и вернётся. **На стройке секция одна**, и поле называет её же: различать ей +нечего, но производность от заголовка индекса сохраняется и там. -| Тип | Поле | Значения | Что это | -| --- | --- | --- | --- | -| `goal` | **Секция** | `Запланировано`, `Направления`, `Сопровождение` | часть роадмапа: состояние очереди | -| прочие | **Категория** | секции беклога проекта (`Ядро`, `Инфра`, …) | полка домена, на которой задача лежит | - -Разные имена потому, что это **разные вещи**. У задачи это полка: куда её -положили и куда вернут, если она уйдёт в работу и вернётся. У цели оно называет -не полку, а место в очереди работ. Одно имя на два смысла их и смешивало; `check` называет -несовпадение дрейфом, `check --fix` переименовывает. +Прежнее имя поля — **«Секция»**: так оно называлось у целей, указывая на часть +роадмапа. Разбор его по-прежнему принимает, `check` называет дрейфом, `check +--fix` переименовывает. Имя самого места принадлежит **заголовку индекса** — файл на него лишь ссылается, и принадлежность сверяется по нижнему регистру. @@ -111,7 +105,8 @@ | --- | --- | | префикс `[goal]` / `[idea]` в H1 | поле **Тип** + эмодзи в H1; `[idea]` → `research` | | тег `kind:<род>` | поле **Тип** (род работы стал типом) | -| поле **Секция** у задачи | поле **Категория** | +| поле **Секция** | поле **Категория** | +| теги `goal:<слаг>` и `decomposed` | сняты: целей больше нет | | поле **Хук** | поле **Зачем** | | мета одной строкой через `·` | мета списком, поле на строку | @@ -147,8 +142,8 @@ забыли назвать, она от отсутствующей не отличает. Раздела нет — отказ во взятии: оценивать нечем. -**У `goal` и `research` раздела нет** — у первой границы называют её задачи, у -второй они становятся известны, когда из разведки родятся задачи. +**У `research` раздела нет** — её границы становятся известны, когда из разведки +родятся задачи. ### Критерии приёмки @@ -166,8 +161,7 @@ что проверено больше проверенного, хуже, чем не проверять вовсе. **У `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, без ведущих, хвостовых и двойных дефисов @@ -261,9 +217,9 @@ проходом по всем местам, где слаг упомянут, иначе останутся битые ссылки, которых никто не проверяет. -## Индексы +## Индекс -Строка везде одной формы: +Строка одной формы: ```markdown - [🐞 Заголовок дословно](items/slug.md) — зачем @@ -276,52 +232,47 @@ | Файл | Что отвечает | Секции | | --- | --- | --- | -| `ROADMAP.md` | что приложение уже умеет и чего ещё не умеет | канонические и в этом порядке: `Запланировано`, `Направления`, `Сопровождение`, `Готово` (англ. `Planned`, `Directions`, `Operations`, `Done`) | -| `BACKLOG.md` | что **можно взять** — только задачи, **в порядке очереди** | категории проекта (по умолчанию Ядро/Инфра) | +| `BACKLOG.md` | что **можно взять**, в значимом порядке | называет проект; на стройке ровно одна (умолчание `План`), на доработке сколько нужно (умолчание `Ядро`/`Инфра`) | | `REJECTED.md` | что ушло без реализации и почему | — | Секции — **единственные заголовки `##` в индексе**: любой другой `##` в преамбуле проверка сочтёт секцией. -**Порядок строк внутри секции беклога значим: это очередь.** Первая строка — то, -что делают следующим; назначает порядок человек на груминге, и двигают его -`move --after` и `move --first`. Одно место из очереди изъято и **производно от -типа и заполненности**: **сырьё** (`research` без раздела «Вопрос») стоит в конце -своей секции, потому что его не берут, и между берущимся оно каждый раз требует +**Порядок строк внутри секции значим, и стадия решает, что он значит:** на +стройке зависимость, на доработке важность (SKILL.md, «Две стадии»). Назначает +его человек — раскладывая шаги или на груминге, — и двигают его `move --after` +и `move --first`. Одно место из очереди изъято и **производно от типа и +заполненности**: **сырьё** (`research` без раздела «Вопрос») стоит в конце своей +секции, потому что его не берут, и между берущимся оно каждый раз требует открыть файл, чтобы это понять. Проверяет `check`, переставляет `check --fix`, -и человек этот порядок не назначает — иначе он был бы приоритетом, которого -здесь нет. +и человек этот порядок не назначает — иначе он был бы решением, которого здесь +нет. **Секции «блокеры» среди них нет.** Блокер — состояние, а не полка: он живёт до ответа человека, а следы остаются вопросами в файлах задач. Постоянно пустая секция со старой семантикой «разбираются пачками» противоречила бы правилу «эскалируем немедленно», поэтому `init` её не заводит, а `check` -говорит о ней в чужом беклоге. Переезжаешь с такой секцией — удали её. В секции -**`Запланировано`** очередь значима и обосновывается прозой; двигают строку -`move --section Запланировано --after <другой>`. В секции **`Готово`** -строки не той формы, что у прочих индексов: дата, слаг, заголовок — как в -`REJECTED.md`, и по той же причине (файла уже нет, ссылаться некуда). +говорит о ней в чужом беклоге. Переезжаешь с такой секцией — удали её. -**Секции роадмапа закреплены** — состав, полнота, единство языка и **порядок** -проверяются `check`; категории беклога проект называет сам. Почему так — -SKILL.md. Порядок закреплён потому, что `Готово` копится: стоя первым, -достигнутое отодвигает за экран то, ради чего роадмап открывают чаще всего. +**Имена секций проект выбирает сам, а количество ограничено стадией:** на +стройке секция одна, потому что порядок там зависимость, и разложенный по полкам +список перестаёт быть планом. Проверяет `check`; слить секции сам он не берётся — +в каком порядке пойдут строки слитых полок, знает только человек. **Заголовок секции пишется с прописной и отбивается пустой строкой с обеих -сторон** — во всех индексах, включая категории беклога, имена которых выбирает -проект. Написание канонических секций и отбивку правит `check --fix`; он же -сводит написание места в мете файла с заголовком индекса. +сторон.** Отбивку правит `check --fix`; он же сводит написание места в мете файла +с заголовком индекса. -Индексы **производны**: расходятся с файлом — правим индексы (`check --fix`). +Индекс **производен**: расходится с файлом — правим индекс (`check --fix`). Строку руками не пишут. Отсюда же ответ на «а если оборвётся посередине». Мутация сперва проверяет всё и складывает правки, и только потом пишет: сначала все временные файлы, потом переименования подряд. Полной транзакции на несколько файлов файловая система не даёт, но окно сжато до цепочки переименований, а **всё, что в нём может -разъехаться, — производное**: файлы целы, индексы восстанавливает `check --fix`. +разъехаться, — производное**: файлы целы, индекс восстанавливает `check --fix`. Поэтому отказ на второй задаче из пяти не оставляет первую переписанной при -нетронутых индексах. +нетронутом индексе. ## `REJECTED.md` @@ -346,27 +297,24 @@ SKILL.md. Порядок закреплён потому, что `Готово` Разметка сверх типа. Тип полем, потому что он один и обязателен; теги — потому что их много и `list --tag` уже умеет отбирать по ним порцию разбора. -- `goal:<слаг>` — цель, которой служит задача. Обязателен **у `feature`**: - новая возможность и есть содержание цели. У `fix`, `chore` и `research` его - может не быть — они служат работоспособности, а не направлению. - `question` — в файле есть неразобранный раздел «Вопросы». -- `decomposed` — на цели: разложена на задачи (см. «Файл цели»). -Тега `kind:<род>` больше нет: род работы стал типом. Оставшийся в файле `check` -называет дрейфом, а `check --fix` снимает, перенеся значение в поле «Тип». +Тегов `kind:<род>`, `goal:<слаг>` и `decomposed` больше нет: род работы стал +типом, а цели упразднены. Оставшиеся в файле `check` называет дрейфом, а `check +--fix` снимает (значение `kind:` при этом переезжает в поле «Тип»). Отбор — `list --tag a,b`: перечисленные через запятую теги требуются **все сразу** (это И, не ИЛИ). Тег, которого нет ни у одной задачи, `list` называет вслух: молчаливый ноль читается как «таких задач нет», а чаще это опечатка. Свои теги проект заводит свободно (партия ревью `review-ГГГГ-ММ-ДД`, тема, -источник) — словарь не фиксирован. В индексы теги не выносим: индексы -производны, отбор делает `list --tag`, а не глаза. +источник) — словарь не фиксирован. В индекс теги не выносим: он +производен, отбор делает `list --tag`, а не глаза. ## Тест «готова к взятию» -Задача готова, если из файла отвечаются четыре вопроса. Первый и четвёртый — -общие, второй и третий у каждого типа свои и перечислены в его файле. +Задача готова, если из файла отвечаются три вопроса. Первый общий, второй и +третий у каждого типа свои и перечислены в его файле. 1. **Что станет наблюдаемо иначе**, когда она сделана — снаружи: пользователю, владельцу сервиса или разработчику. «Отрефакторить X» — не ответ; «перестанет @@ -379,26 +327,13 @@ SKILL.md. Порядок закреплён потому, что `Готово` `chore` — `Затрагивает`. 3. **По чему видно, что закончено** — критерии приёмки с оракулами; у `research` вместо них `Куда ляжет ответ`. -4. **Какую часть «Завершения» своей цели она двигает** — у задачи с целью. - Строкой: «двигает пункт 2 «Завершения» — накопительная метрика перестаёт - уменьшаться». Это и есть защита от задачи «отрефакторить X»: она проваливает - тест не потому, что невидима снаружи, а потому, что не находит строки, к - которой относится. Заодно видно обратное — достаточен ли набор задач для - цели: строка «Завершения», к которой не относится ни одна задача, это - незакрытая часть возможности. - - **У задачи без цели** (`fix`, `chore`, `research`) вопрос не задаётся: они - служат работоспособности, а не направлению. - -Не отвечается первый, второй или третий вопрос → это ещё не задача, а **сырьё**: -тип `research` без раздела «Вопрос», место — конец секции, работа над ним — -штурм. Не отвечается четвёртый у `feature` → либо цель есть и не проставлена, -либо это не новая возможность. +Не отвечается любой из трёх → это ещё не задача, а **сырьё**: тип `research` без +раздела «Вопрос», место — конец секции, работа над ним — штурм. Отвечается всё, но задача не делается одним заходом и не мерджится целиком → -это **несколько задач под одной целью**, дроби сразу. Промежуточного зонтика -между целью и задачей нет: тип `[epic]` упразднён, потому что зонтиком стала -сама цель. +это **несколько задач**, дроби сразу и ставь их в списке подряд. Промежуточного +зонтика между планом и задачей нет: тип `[epic]` упразднён, и цель, ставшая +зонтиком после него, упразднена тоже. Тест применяется при заведении и при переоценке. К старым задачам, которых операция не касается, задним числом не применяется — беклог не переоформляют diff --git a/av-dev/skills/task-track/references/task-goal.md b/av-dev/skills/task-track/references/task-goal.md deleted file mode 100644 index 0828443..0000000 --- a/av-dev/skills/task-track/references/task-goal.md +++ /dev/null @@ -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 --reason "<почему>"`; задача, переживающая цель, — `edit - --goal <другая>`. **Причина обязательна и пишется своя каждой:** «цель - отменена» это не причина, а пересказ команды, и в `REJECTED.md` от него нет - пользы через квартал. -2. **Закрыть саму цель** — `close <слаг> --reason "<почему замысел отменён>"`. - Файл удаляется, строка с причиной и датой уходит в `REJECTED.md`. В `Готово` - не попадает: `Готово` отвечает «что приложение умеет», а отменённая цель не - умеет ничего. - -**Место этому — груминг, а не отдельный заход.** Отмена цели значит -разбор всех её задач, а разбор задач и есть шаг 3 груминга -(скилл `task-groom`, «что перестало быть важным»). Отменять на ходу, -между делом, — верный способ закрыть скопом то, что стоило перевесить. - -## Что видит машина, а что человек - -`check` считает цели, различает разобранные и пустые, ставит `decomposed`, -запрещает закрыть цель с живыми задачами и держит `Секцию` в согласии с -заголовком роадмапа. **Годность формулировки — не машине**: «возможность это или -область работ» решает [агент вычитки](../SKILL.md#вычитка-два-прохода-а-не-один). - -Достигнутая цель **не исчезает**: «что приложение умеет» — половина вопроса, ради -которого роадмап открывают. Вторым домом поведения роадмап при этом не -становится: нормативное поведение живёт в `openspec/specs/`, роадмап отвечает, -**когда и в каком порядке** оно появилось. diff --git a/av-dev/skills/task-track/references/task-research.md b/av-dev/skills/task-track/references/task-research.md index e73547b..1821afa 100644 --- a/av-dev/skills/task-track/references/task-research.md +++ b/av-dev/skills/task-track/references/task-research.md @@ -14,7 +14,6 @@ | Обязательные разделы | `Вопрос`, `Куда ляжет ответ` | | Допустимые сверх того | `Рамки`, `Вопросы` | | Поле места | **Категория** — полка домена беклога | -| Цель (`goal:<слаг>`) | нет | | Индекс | `BACKLOG.md` | | Берётся в работу | да — **но только с заполненным «Вопросом»** | diff --git a/av-dev/skills/task-track/scripts/tasks.py b/av-dev/skills/task-track/scripts/tasks.py index 370d545..2e0fcb5 100755 --- a/av-dev/skills/task-track/scripts/tasks.py +++ b/av-dev/skills/task-track/scripts/tasks.py @@ -1,42 +1,50 @@ #!/usr/bin/env python3 """Детерминированный инструмент управления задачами: файлы против индексов. -Преемник backlog.py. Разница по существу одна: **секция-как-уровень заменена -целью** (`goal:<слаг>` тегом), а приоритет стал тем, чем он и является, — -**порядком строк в беклоге**. Индексов два — беклог и роадмап, — и задача живёт -ровно в одном из них за раз. `REJECTED.md` индексом не считается: он не говорит, -где запись числится, он кладбище ушедшего. +Приоритет — не поле, а **порядок строк в беклоге**. Индекс один: беклог. +`REJECTED.md` индексом не считается: он не говорит, где запись числится, он +кладбище ушедшего. Раскладка. Путь каталога — `tasks/` в корне репозитория по умолчанию; другой называется ключом `[tasks] dir`. Каталог принадлежит этому скиллу, а не канону документов: учёт работ ведут и в проекте, который к канону не приведён. Имена -частей и **версия раскладки** живут в `.av-dev.toml` в корне; журнал версий — -references/changelog.md скилла canon. +частей, **стадия проекта** и **версия раскладки** живут в `.av-dev.toml` в +корне; журнал версий — references/changelog.md скилла canon. tasks/ - items/ задачи и цели файлами, .md - ROADMAP.md состояние проекта: что уже умеет и чего ещё не умеет. - Секции канонические и в этом порядке: Запланировано | - Направления | Сопровождение | Готово (или Planned | - Directions | Operations | Done — один язык на индекс) - BACKLOG.md что можно взять — только задачи, целей здесь нет. - Порядок строк внутри секции значим: это очередь + items/ задачи файлами, .md + BACKLOG.md что можно взять. Порядок строк внутри секции значим, + но значит он **разное на разных стадиях** (см. ниже) REJECTED.md ушедшее БЕЗ реализации, с причиной и датой -Источник истины — файл задачи в items/. Индексы производны: расходятся — +Источник истины — файл задачи в items/. Индекс производен: расходятся — неправ индекс. **«Зачем» живёт в мете файла**, а не только в строке индекса: иначе восстановление пропавшей строки (`check --fix`) теряло бы его навсегда. -Исключений из производности два, и оба намеренные: **в каком индексе лежит -запись — знают индексы** (поля-состояния в файле нет), и **порядок строк в -беклоге** — приоритет это свойство очереди, а не задачи, и в файле ему места -нет. Рассогласование по обоим ловит check. +Исключение из производности одно и намеренное: **порядок строк в беклоге** — +приоритет это свойство очереди, а не задачи, и в файле ему места нет. +Рассогласование ловит check. -Тип — единственная ось записи и **закрытый словарь из пяти значений**: -goal | feature | fix | chore | research, по-английски, как и прочие токены -команд. Дом типа — **поле меты `Тип` первой строкой**; эмодзи в заголовке H1 -производна от него, её ставит и чинит `check --fix`. Эмодзи нужна там, где -принимают решение «брать или не брать», — в строке индекса, а она копирует H1 -дословно. +**Стадия проекта — ось, и она решает, что значит порядок строк.** + + build (стройка) — приложение ещё строится. Беклог = план от базы к + деталям, **секция ровно одна**, порядок = зависимость: + раньше нельзя. Список пишется вперёд целиком, и это не + гниение беклога, а замысел. Пустой беклог значит, что + стройка окончена. + support (доработка) — приложение работает, правки точечные. Секции — полки + домена, порядок внутри полки = важность: раньше лучше. + Заводится по одной, по мере появления; пустой беклог — + нормальное состояние. + +Стадия объявляется ключом `[tasks] stage` и меняется командой `stage`. Молчание +ответом не считается: без ключа check отказывает, потому что читать порядок +строк не по чему. + +Тип — вторая ось записи и **закрытый словарь из четырёх значений**: +feature | fix | chore | research, по-английски, как и прочие токены команд. Дом +типа — **поле меты `Тип` первой строкой**; эмодзи в заголовке H1 производна от +него, её ставит и чинит `check --fix`. Эмодзи нужна там, где принимают решение +«брать или не брать», — в строке индекса, а она копирует H1 дословно. Прежних осей было две: тип записи (goal | idea | task) и род работы (`kind:<род>` тегом). Ортогональность была фальшивой — из двенадцати клеток @@ -45,34 +53,33 @@ goal | feature | fix | chore | research, по-английски, как и пр он значил не род работы, а **состояние незаполненности**, и это состояние теперь называется честно — `research` без раздела «Вопрос». -**Цель — возможность приложения**, а не тема работ: она отвечает на вопрос «что -приложение будет уметь», её «Завершение» — наблюдаемый признак того, что уже -умеет. Задача — шаг к этой возможности, она отвечает на «что для этого нужно -сделать». Отсюда роадмап и есть состояние проекта: достигнутая цель не -исчезает, а переезжает строкой с датой в секцию достигнутого. - -**Цель обязательна не у всякой задачи.** Новая возможность (`feature`) без цели -не бывает — цель и есть её содержание. Починка, техдолг и разведка служат -работоспособности, а не направлению, и живут без цели законно. +**Тип `goal` и роадмап упразднены.** Цель была зонтиком над параллельными +направлениями — она нужна там, где список работ нельзя выстроить в один +порядок. У проекта, который ведёт один человек, такого не бывает: на стройке +список линеен по зависимости, на доработке правки независимы. Половину своего +вопроса роадмап при этом дублировал беклогом («чего ещё не умеет» = «что +осталось в списке»), а вторую половину («что уже умеет») отвечают спеки и +`git log` индекса. **Тип определяет схему записи**: какие разделы тела обязательны, какие -допустимы, нужна ли цель, берётся ли запись в работу. Схема — TYPE_SCHEMA; -проза с алгоритмом работы над каждым типом — `references/task-<тип>.md`. +допустимы, берётся ли запись в работу. Схема — TYPE_SCHEMA; проза с алгоритмом +работы над каждым типом — `references/task-<тип>.md`. Использование: - tasks.py init [--dir DIR] [--sections …] [--items …] [--backlog …] … + tasks.py init --stage build|support [--dir DIR] [--sections …] [--items …] … + tasks.py stage [build|support] [--sections …] [--dir DIR] tasks.py check [--dir DIR] [--fix] tasks.py list [--dir DIR] [--stale] [--section S] [--type T] [--tag a,b] - [--goal S] [--index backlog|roadmap|all] [--questions] - tasks.py add --slug S --title T --type goal|feature|fix|chore|research - [--section S] [--goal G] [--why H] [--reason R] [--tag a,b] [--dir DIR] - tasks.py edit S [--title T] [--why H] [--type T] [--goal G] - [--add-tag a,b] [--rm-tag c,d] [--section S] [--dir DIR] + [--raw] [--questions] + tasks.py add --slug S --title T --type feature|fix|chore|research + [--section S] [--why H] [--reason R] [--tag a,b] [--dir DIR] + tasks.py edit S [--title T] [--why H] [--type T] + [--add-tag a,b] [--rm-tag c,d] [--dir DIR] tasks.py move S [--section S] [--reason R] [--after S | --first] [--dir DIR] tasks.py close S (--reason R | --implemented) [--dir DIR] tasks.py reopen S [--reason R] [--dir DIR] tasks.py ready S [S …] [--dir DIR] - tasks.py adopt scan --from PATH [PATH …] [--target DIR] [--out PLAN.json] + tasks.py adopt scan --from PATH [PATH …] --stage S [--target DIR] [--out PLAN.json] tasks.py adopt apply --plan PLAN.json [--refs PATH …] [--dry-run] Каталог задач: `--dir` (обязан быть внутри рабочего каталога) → `tasks/` вверх @@ -149,20 +156,28 @@ EXIT_USAGE = 2 EXIT_ENV = 3 EXIT_INTERNAL = 4 -# Ключ `dir` в DEFAULTS не входит намеренно: он говорит, **где** каталог, а не -# как названы его части, и в `Layout` (тот про имена внутри) ему делать нечего. +# Ключи `dir` и `stage` в DEFAULTS не входят намеренно: первый говорит, **где** +# каталог, второй — **на какой стадии проект**, и ни один не называет имени +# части. В `Layout` (тот про имена внутри) им делать нечего. DIR_KEY = "dir" DEFAULT_DIR = "tasks" +# Стадия проекта — ось, и решает она, что значит порядок строк беклога. +# На стройке порядок это зависимость (раньше нельзя), на доработке — важность +# (раньше лучше). Отсюда и всё остальное: сколько у беклога секций, как его +# пополняют и что означает его опустошение. +STAGE_KEY = "stage" +BUILD, SUPPORT = "build", "support" +STAGES = (BUILD, SUPPORT) +STAGE_RU = {BUILD: "стройка", SUPPORT: "доработка"} + DEFAULTS = { "items": "items", "backlog": "BACKLOG.md", - "roadmap": "ROADMAP.md", "rejected": "REJECTED.md", "criteria_heading": "Критерии приёмки", "surface_heading": "Затрагивает", "questions_heading": "Вопросы", - "completion_heading": "Завершение", "repro_heading": "Воспроизведение", "question_heading": "Вопрос", "answer_heading": "Куда ляжет ответ", @@ -172,32 +187,12 @@ DEFAULTS = { # Какие ключи конфига — имена файлов и каталогов (их существование сверяется # с диском первым делом, иначе кривой ключ выглядит как пропавший файл). -PATH_KEYS = ("items", "backlog", "roadmap", "rejected") +PATH_KEYS = ("items", "backlog", "rejected") -DEFAULT_SECTIONS = "Ядро,Инфра" - -# Секции роадмапа **канонические**, в отличие от секций беклога. Причина не в -# любви к единообразию: у каждой своя семантика — достигнутое, очередь, долгие -# направления, работа по сопровождению, — в достигнутое пишет сам `close`, и роадмап, -# названный по-своему, читался бы только своим автором. Секции беклога семантики -# не несут, это полки, и остаются делом проекта. -# -# Пара на секцию: русское имя и английское. Проект держит **один язык на весь -# индекс** — вперемешку это дрейф, который check называет вслух. Сверка везде -# идёт по нижнему регистру, а пишется — как здесь: заголовок предложением, с -# прописной. -# Порядок значим и проверяется: достигнутое **копится**, и стоя первым оно со -# временем отодвигает за экран всё, ради чего роадмап открывают. -ROADMAP_SECTIONS = ( - ("Запланировано", "Planned"), # очередь значима, обоснована прозой - ("Направления", "Directions"), # очереди нет, тянутся долго - ("Сопровождение", "Operations"), # чем держат проект, а не что умеет приложение - ("Готово", "Done"), # достигнутое: что приложение уже умеет -) -# Позиции — из самого кортежа, а не числами: переставили секцию — индексы -# переехали сами. -PLANNED, DIRECTIONS, OPERATIONS, ACHIEVED = range(len(ROADMAP_SECTIONS)) -DEFAULT_ROADMAP_SECTIONS = ",".join(ru for ru, _ in ROADMAP_SECTIONS) +# Умолчания секций по стадиям. На доработке это **полки домена**: смысла они не +# несут, называет их проект. На стройке секция ровно одна — список от базы к +# деталям, — и её имя тоже дело проекта: различать ей нечего, она одна. +DEFAULT_SECTIONS = {BUILD: "План", SUPPORT: "Ядро,Инфра"} # Мета — список под заголовком, поле на строку. Старая форма (все поля одной # строкой через `·`) читается по-прежнему: у проектов на диске лежат файлы в @@ -215,10 +210,11 @@ WHY_KEYS = ("зачем", "why", "хук", "hook") # закрытым списком — единственный способ не спутать поле меты со строкой тела # вида `- **Важно:** …`. TYPE_KEYS = ("тип", "type") -# «Секция» — прежнее имя поля, оставшееся у цели: она указывает на часть -# роадмапа, а это состояние очереди, а не полка домена. У всех прочих типов -# поле называется «Категория». Разбор принимает оба ключа у любого типа, чтобы -# файлы переезжали сами; `check` называет несовпадение дрейфом, `--fix` правит. +# Поле места называется **«Категория»**: у задачи оно называет полку беклога, на +# которой она лежит. «Секция» — прежнее имя, оставшееся от целей и роадмапа; +# разбор принимает его по-прежнему, чтобы файлы переезжали сами, а `check --fix` +# переименовывает. +PLACE_KEY = "Категория" PLACE_KEYS = ("категория", "category", "секция", "section") META_KEYS = {*TYPE_KEYS, *PLACE_KEYS, "теги", "tags", *WHY_KEYS} @@ -260,37 +256,33 @@ SECTION = re.compile(r"^##\s+(.+?)\s*$") TYPE_PREFIX = re.compile(r"^\[(.+?)\]\s*(.*)$") SLUG_RE = re.compile(r"[a-z0-9]+(?:-[a-z0-9]+)*") SLUG = re.compile(SLUG_RE.pattern + r"\.md") -GOAL_LINE = re.compile(r"^(?:-\s+)?\*\*(?:Цель|Goal):\*\*\s*\[(.+?)\]\((.+?\.md)\)") DATE_RE = re.compile(r"\d{4}-\d{2}-\d{2}") BULLET = re.compile(r"^[-*]\s+(.*)$") # Строка кладбища: - ГГГГ-ММ-ДД `slug` — текст REJECTED_ENTRY = re.compile(r"^- \d{4}-\d{2}-\d{2} `[a-z0-9-]+` — .+") -GOAL = "goal" RESEARCH = "research" -# Тип — единственная ось и **закрытый словарь**. Открытый разъедется на -# синонимах (`bug`, `bugfix`, `fix`, `defect`), и отбор по типу перестанет -# отвечать на свой единственный вопрос. Ни один тип не подходит — это сигнал, -# что в записи их два и её надо разделить. -TYPES = ("goal", "feature", "fix", "chore", RESEARCH) +# Тип — ось записи и **закрытый словарь**. Открытый разъедется на синонимах +# (`bug`, `bugfix`, `fix`, `defect`), и отбор по типу перестанет отвечать на +# свой единственный вопрос. Ни один тип не подходит — это сигнал, что в записи +# их два и её надо разделить. +TYPES = ("feature", "fix", "chore", RESEARCH) # Эмодзи **производна от типа**, а не второй его дом: её ставит `add` и чинит # `check --fix`. Живёт в H1 потому, что строка индекса копирует заголовок # дословно, — так тип виден там, где решают «брать или не брать», и инвариант # «заголовок в индексе дословно» остаётся нетронутым. -TYPE_EMOJI = {"goal": "🎯", "feature": "✨", "fix": "🐞", - "chore": "🧹", RESEARCH: "🔬"} +TYPE_EMOJI = {"feature": "✨", "fix": "🐞", "chore": "🧹", RESEARCH: "🔬"} EMOJI_TYPE = {v: k for k, v in TYPE_EMOJI.items()} -TAKEABLE = ("feature", "fix", "chore", RESEARCH) # что берётся в работу +TAKEABLE = TYPES # берутся в работу все четыре: целей больше нет # Заголовок в форме действия требуется там, где исход работы — изменение -# системы. У цели он называет возможность, у разведки — предмет: её исход -# знание, и заголовок-действие обещал бы решённость, которой ещё нет. +# системы. У разведки он называет предмет: её исход знание, и заголовок-действие +# обещал бы решённость, которой ещё нет. ACTION_TYPES = ("feature", "fix", "chore") QUESTION_TAG = "question" -GOAL_TAG = "goal:" -# Цель обязательна только у новой возможности: цель и есть возможность. -# Починка, техдолг и разведка служат работоспособности, а не направлению — -# придуманная им цель это то же враньё, от которого спасает тип. -NEEDS_GOAL = ("feature",) +# Прежний дом направления. Тип `goal` упразднён вместе с роадмапом; тег +# читается только затем, чтобы `check` назвал его вслух, а `--fix` снял. +LEGACY_GOAL_TAG = "goal:" +LEGACY_GOAL = "goal" # Схема тела на тип: какие разделы обязательны, какие ещё допустимы. Значения — # **ключи конфига**, а не сами заголовки: имена заголовков проект настраивает, @@ -302,7 +294,6 @@ NEEDS_GOAL = ("feature",) # а вот раздел, которого тип не предполагает, чаще всего означает, что тип # проставлен не тот. TYPE_SCHEMA = { - "goal": {"required": ("completion_heading",), "allowed": ()}, "feature": {"required": ("surface_heading", "criteria_heading"), "allowed": ("scope_heading", "questions_heading")}, "fix": {"required": ("repro_heading", "surface_heading", "criteria_heading"), @@ -316,7 +307,7 @@ TYPE_SCHEMA = { # `check --fix` переносит значение в поле «Тип», снимает тег и ставит эмодзи. LEGACY_KIND_TAG = "kind:" LEGACY_IDEA = "idea" # тип `[idea]` упразднён: это research без «Вопроса» -DECOMPOSED_TAG = "decomposed" # цель разложена на задачи (см. «Статус цели») +LEGACY_DECOMPOSED_TAG = "decomposed" # ставился цели, разложенной на задачи STALE_DAYS = 180 # порог «залежалась» для метрики здоровья в check CRITERIA_MIN, CRITERIA_MAX = 2, 5 # сколько утверждений в критериях приёмки BODY_PLACEHOLDER = "\n") - insert_entry(roadmap_lines, g["section"].lower(), - entry_line(lay, title, g["slug"], g.get("why", ""))) - for it in pl.get("items", []): slug = it.get("slug") or it["old_slug"] if it.get("old_slug") and it["old_slug"] != slug: @@ -3163,8 +2944,6 @@ def cmd_adopt_apply(a: argparse.Namespace) -> int: rtype = (it.get("type") or "").lower() title = h1_of(rtype, it["title"]) tags = list(it.get("tags", [])) - if it.get("goal"): - tags.append(f"{GOAL_TAG}{it['goal']}") body = it.get("body", "") if not body and it.get("source") and Path(str(it["source"]).split(":")[0]).is_file(): src = Path(str(it["source"]).split(":")[0]) @@ -3189,7 +2968,7 @@ def cmd_adopt_apply(a: argparse.Namespace) -> int: entry_line(lay, title, slug, it.get("why", ""))) if pl.get("rejected"): - head = init_files(lay, sections, roadmap_sections, {})[lay.index("rejected")] + head = init_files(lay, sections, stage_name, {})[lay.index("rejected")] body = [] for line in pl["rejected"]: line = re.sub(r"Был приоритет:", "Была секция:", line) @@ -3198,8 +2977,10 @@ def cmd_adopt_apply(a: argparse.Namespace) -> int: body.append(line) wr.file(lay.index("rejected"), head + "\n".join(body) + "\n") - wr.file(lay.index("backlog"), "\n".join(backlog_lines)) - wr.file(lay.index("roadmap"), "\n".join(roadmap_lines)) + # Через `index`, а не `file`: отбивка секций живёт на записи индекса, и + # каталог, собранный в обход неё, встречал бы человека ошибкой `check` + # на первом же прогоне. + wr.index(lay, "backlog", backlog_lines) if a.dry_run: print(f"пробный прогон: записалось бы файлов {len(wr.writes)}," @@ -3209,8 +2990,9 @@ def cmd_adopt_apply(a: argparse.Namespace) -> int: wr.commit() # Настройки — тем же проходом, что и у `init`, и по той же причине: каталог, # собранный здесь, обязан быть назван в `.av-dev.toml`, иначе следующая же - # команда не найдёт его и уйдёт искать умолчание. - said = write_config(lay.project, adopt_cfg(lay)) + # команда не найдёт его и уйдёт искать умолчание. Стадия там же и по той же + # причине: без неё `check` откажет на первом же прогоне. + said = write_config(lay.project, {**adopt_cfg(lay), STAGE_KEY: stage_name}) # --- перекрёстные ссылки: тем же проходом, иначе они останутся битыми --- ref_paths: list[Path] = [*lay.items.glob("*.md"), lay.index("rejected")] @@ -3220,10 +3002,10 @@ def cmd_adopt_apply(a: argparse.Namespace) -> int: touched, per_slug = rewrite_refs(ref_paths, renames, [tuple(pair) for pair in pl.get("path_map", [])], False) - print(f"каталог задач собран: {root}") + print(f"каталог задач собран: {root}, стадия {STAGE_RU[stage_name]}") for line in said: print(f" {line}") - print(f" целей {len(pl.get('goals', []))}, задач {len(pl.get('items', []))}," + print(f" задач {len(pl.get('items', []))}," f" строк кладбища {len(pl.get('rejected', []))}") print(f" переименовано слагов: {len(renames)};" f" ссылок поправлено: {sum(per_slug.values())} в {touched} файлах") @@ -3236,23 +3018,23 @@ def cmd_adopt_apply(a: argparse.Namespace) -> int: # --- честно про переходное состояние: считаем по написанным файлам --- written = tasks_of(lay) - # Цель обязательна только у новой возможности: fix, chore и research живут - # без неё законно, и check об этом молчит. - no_goal = [n for n, t in written.items() if t["type"] in NEEDS_GOAL and not t["goal"]] unfit = [n for n, t in written.items() if t["type"] in TAKEABLE and schema_verdict(lay, t)[0]] print("\nпереходное состояние — назови его в докладе целиком:") - print(f" задач типа {'/'.join(NEEDS_GOAL)} без цели: {len(no_goal)} — это ОШИБКИ check" - f" (правится `tasks.py edit <слаг> --goal <цель>`)" - + (f": {', '.join(sorted(x[:-3] for x in no_goal)[:5])}…" if no_goal else "")) print(f" задач, не собравших разделы своего типа: {len(unfit)} —" f" check это ошибкой не считает, но `ready` их не пропустит:" f" брать сегодня физически нечего") print(" закрывается порциями груминга по 5–8 задач (скилл groom):" - " проставить цели, превратить «готово, когда» в критерии с оракулами," - " вынуть вопросы из прозы в раздел. Готовность к первой задаче —" - " не «check зелёный», а «`ready` пропускает хотя бы верхние строки" - " очереди»: берут по одной, и годной обязана быть та, которую берут.") + " превратить «готово, когда» в критерии с оракулами, вынуть вопросы" + " из прозы в раздел. Готовность к первой задаче — не «check зелёный»," + " а «`ready` пропускает хотя бы верхние строки очереди»: берут по" + " одной, и годной обязана быть та, которую берут.") + print(" порядок строк проверь глазами: " + + ("на стройке он значит зависимость, и выведен он из нумерации" + " источника — там, где её не было, порядок случаен" + if stage_name == BUILD else + "на доработке он значит важность, и машина её не знает: очередь" + " расставляется первым же грумингом")) print(" источники не удалены: сверь глазами и убери сам" f" ({', '.join(pl.get('sources', []))}) — удалять чужое молча нельзя.") print(" подписи ссылок машина не трогает: цель ссылки поправлена, а текст" @@ -3269,54 +3051,48 @@ def main() -> int: p.add_argument("--fix", action="store_true", help="починить безопасный дрейф (секция, заголовок, дубли, «зачем», форма меты)") - p = sub.add_parser("list", help="список задач и целей") + p = sub.add_parser("list", help="список задач") p.add_argument("--dir") p.add_argument("--stale", action="store_true", help="от самой залежавшейся") - p.add_argument("--section", help="категория беклога или часть роадмапа") + p.add_argument("--section", help="категория беклога") p.add_argument("--type", choices=TYPES) - p.add_argument("--tag", help="тег или список через запятую (нужны ВСЕ):" - " goal:<слаг>, question") - p.add_argument("--goal", help="задачи одной цели (перечень выводится, а не хранится)") + p.add_argument("--tag", help="тег или список через запятую (нужны ВСЕ): question") p.add_argument("--raw", action="store_true", help=f"только сырьё: {RESEARCH} без раздела «Вопрос»") - p.add_argument("--index", choices=("backlog", "roadmap", "all")) p.add_argument("--questions", action="store_true", help="только с открытым вопросом") - p = sub.add_parser("add", help="завести запись: цель, задачу или разведку") + p = sub.add_parser("add", help="завести задачу или разведку") p.add_argument("--dir") p.add_argument("--slug", required=True) p.add_argument("--title", required=True) p.add_argument("--type", choices=TYPES, required=True, - help="тип решает схему записи: разделы, цель, право на взятие") - p.add_argument("--section", help="категория беклога или часть роадмапа") - p.add_argument("--goal", help="слаг цели → тег goal:<слаг>") + help="тип решает схему записи: разделы и право на взятие") + p.add_argument("--section", help="категория беклога") p.add_argument("--why") p.add_argument("--reason") p.add_argument("--tag") - p = sub.add_parser("edit", help="сменить заголовок/«зачем»/тип/цель/теги") + p = sub.add_parser("edit", help="сменить заголовок/«зачем»/тип/теги") p.add_argument("slug") p.add_argument("--title") p.add_argument("--why") p.add_argument("--type", choices=TYPES) - p.add_argument("--goal", help="заменить тег goal:<слаг>") p.add_argument("--add-tag", dest="add_tag") p.add_argument("--rm-tag", dest="rm_tag") - p.add_argument("--section", help="только вместе со сменой типа, меняющей индекс") p.add_argument("--dir") p = sub.add_parser("move", help="переставить строку: место в очереди или другая секция") p.add_argument("slug") - p.add_argument("--section", help="другая категория беклога или часть роадмапа;" + p.add_argument("--section", help="другая категория беклога;" " без него — текущая секция записи") p.add_argument("--reason") g = p.add_mutually_exclusive_group() - g.add_argument("--after", help="встать следом за этим слагом — расстановка" - " приоритета: порядок строк беклога это очередь") + g.add_argument("--after", help="встать следом за этим слагом: на стройке это" + " зависимость, на доработке — приоритет") g.add_argument("--first", action="store_true") p.add_argument("--dir") - p = sub.add_parser("close", help="закрыть задачу или цель") + p = sub.add_parser("close", help="закрыть задачу") p.add_argument("slug") g = p.add_mutually_exclusive_group(required=True) g.add_argument("--reason", help="ушла без реализации → строка в REJECTED") @@ -3334,19 +3110,31 @@ def main() -> int: p = sub.add_parser("init", help="завести каталог задач в новом проекте") p.add_argument("--dir") - p.add_argument("--sections", default=DEFAULT_SECTIONS) + # Стадия обязательна: умолчания у неё нет и быть не может. Подставленное + # значение отвечало бы за человека на единственный вопрос, который тут + # вообще задаётся, — строится приложение или живёт. + p.add_argument("--stage", choices=STAGES, required=True, + help="build — беклог это план стройки (порядок = зависимость);" + " support — очередь правок (порядок = важность)") + p.add_argument("--sections", help="секции беклога; на стройке ровно одна") p.add_argument("--items") p.add_argument("--backlog") - p.add_argument("--roadmap") p.add_argument("--rejected") + p = sub.add_parser("stage", help="показать или сменить стадию проекта") + p.add_argument("to", nargs="?", choices=STAGES, + help="без него — показать текущую") + p.add_argument("--sections", help="секции беклога новой стадии") + p.add_argument("--dir") + p = sub.add_parser("adopt", help="вывести каталог задач из того, что уже есть в репозитории") asub = p.add_subparsers(dest="adopt_command", required=True) s = asub.add_parser("scan", help="только карта: что найдено и как разложилось") s.add_argument("--from", dest="sources", nargs="+", required=True) + s.add_argument("--stage", choices=STAGES, required=True) s.add_argument("--target", default="tasks") s.add_argument("--out", default="tasks-adopt-plan.json") - s.add_argument("--sections", default=DEFAULT_SECTIONS) + s.add_argument("--sections", help="секции беклога; на стройке ровно одна") s = asub.add_parser("apply", help="записать каталог по подтверждённой карте") s.add_argument("--plan", required=True) @@ -3368,6 +3156,7 @@ def main() -> int: "close": lambda: cmd_close(lay, a), "reopen": lambda: cmd_reopen(lay, a), "ready": lambda: cmd_ready(lay, a), + "stage": lambda: cmd_stage(lay, a), }[a.command]() diff --git a/scripts/addresses.py b/scripts/addresses.py index 96956a7..af2e866 100644 --- a/scripts/addresses.py +++ b/scripts/addresses.py @@ -10,7 +10,7 @@ куда большей вероятностью, чем новая тема. Адрес документа принадлежит одному скиллу, а называют его все: `docs/*` стоит -примерно в сорока местах конвейера, `tasks/ROADMAP.md` — в четырёх местах +примерно в сорока местах конвейера, `tasks/BACKLOG.md` — в нескольких местах канона. Переименование в каноне до этих мест не доходит. **Почему тут нужна машина, а не аккуратность.** Прогон ревью умеет честно