канон 4: секция «Сопровождение», «Готово» вниз, порядок закреплён
healthlog уже переехал на канон 3, а переименование секции я внёс правкой записи версии 3 задним числом — то есть переписал текст, по которому он ехал. Посылка «ни один проект на каноне 3 не стоит» была ложной, решение ШШШ отменено. Запись версии — черновик ровно до первого переехавшего проекта. После этого она история, и любое изменение канона заводит новую версию, даже если меняется одно слово. Проверять дёшево: grep '"canon"' по живым проектам. Дорого обратное — проект, повышенный по тексту, которого больше не существует, невоспроизводим. Запись версии 3 восстановлена дословно (Разработка | Tooling), переименование уехало в версию 4. jellybit, стоящий на каноне 2, прочтёт обе записи подряд и заведёт Разработка, чтобы через шаг переименовать; в шаг версии 3 добавлена оговорка «едешь сразу на 4 — заводи Готово последней и не переставляй дважды». «Готово» переехало вниз, и порядок секций стал каноническим. Достигнутое копится: через год этой секции больше, чем всех остальных вместе, и стоя первой она отодвигает за экран то, ради чего роадмап открывают чаще всего. Порядок проверяет roadmap_lint, переставляет check --fix — вместе с содержимым секций, потому что двигать десяток строк руками это работа, на которой ошибаются. Чужую секцию перестановка не трогает вовсе: её место в порядке неизвестно. Индексы позиций считаются из самого кортежа: ACHIEVED был 0 и стал 3, хардкод пережил бы перестановку молча и сломал бы close. Обкатка нашла два дефекта оформления, оба порождённые самой перестановкой. Отбивка нужна и перед заголовком — сдвиг блоков ставит два заголовка вплотную. Удаление строки индекса оставляет две пустые подряд, и пустоты копятся. Проверка оформления теперь сверяется с самим нормализатором, а не своим набором условий: два описания одного правила разъедутся, и check начнёт молчать о том, что --fix правит. CANON_VERSION = 4 в docs.py, примеры .pm.json в canon.md и skeletons.md. DECISIONS тема 26 (ЭЭЭ, ЮЮЮ, ЯЯЯ, следствия 98–100). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+58
-5
@@ -1746,11 +1746,13 @@ ADR, запискам разведки и сообщениям коммитов
|
||||
смысл — сошлись на смысле, потому что метрики, логи, инфраструктура и выкладка
|
||||
в эту секцию просятся и так.
|
||||
|
||||
**ШШШ. Версия канона не менялась, и это законно.** Ни один проект на каноне 3 не
|
||||
стоит: healthlog и jellybit держат канон 2, повышение только предстоит. Запись
|
||||
версии 3 правится **как черновик**, а не как история: тот, кто по ней поедет,
|
||||
увидит сразу `Сопровождение`. Версия отделяет одно **состояние проектов** от
|
||||
другого, а не одну редакцию текста от другой.
|
||||
**ШШШ. Версия канона не менялась, и это законно.** ~~Ни один проект на каноне 3
|
||||
не стоит: healthlog и jellybit держат канон 2, повышение только предстоит.~~
|
||||
|
||||
**Отменено в тот же день (тема 26).** Посылка была ложной: healthlog уже переехал
|
||||
на канон 3, и правка записи версии 3 задним числом переписывала то, по чему он
|
||||
ехал. Правило осталось верным, применение — нет: черновиком запись версии
|
||||
является ровно до того, как **первый** проект по ней поехал.
|
||||
|
||||
**ЩЩЩ. Сопровождение и эксплуатация — целое и часть, и словарь у трёх мест
|
||||
общий.** Тема живёт в трёх документах, и раньше каждое место говорило своим
|
||||
@@ -1781,3 +1783,54 @@ ADR, запискам разведки и сообщениям коммитов
|
||||
переименовании `Разработка` → `Сопровождение` проверка назвала секцию
|
||||
роадмапа чужой и остановилась: регистр она правит сама, смысл — нет. Ровно
|
||||
то поведение, которое нужно проекту при повышении канона.
|
||||
|
||||
## 26. Канон 4: правка задним числом отменена (2026-08-04)
|
||||
|
||||
### Что было
|
||||
|
||||
Секцию `Разработка` переименовали в `Сопровождение` без повышения версии канона —
|
||||
на посылке «ни один проект на каноне 3 не стоит» (тема 25, ШШШ). Посылка
|
||||
оказалась ложной: healthlog уже переехал, `docs/.pm.json` держит `"canon": 3`, а
|
||||
роадмап — секцию `Разработка` с прописной. Правка записи версии 3 переписывала
|
||||
то, по чему он ехал.
|
||||
|
||||
### Решено
|
||||
|
||||
**ЭЭЭ. Запись версии — черновик ровно до первого переехавшего проекта.** После
|
||||
этого она **история**, и любое изменение канона заводит новую версию, даже если
|
||||
меняется одно слово. Проверять это дёшево: `grep '"canon"' */docs/.pm.json` по
|
||||
живым проектам. Дорого — обратное: проект, повышенный по тексту, которого больше
|
||||
не существует, невоспроизводим.
|
||||
|
||||
Запись версии 3 восстановлена дословно (`Разработка` | `Tooling`), переименование
|
||||
уехало в версию 4. jellybit, стоящий на каноне 2, прочтёт обе записи подряд и
|
||||
заведёт `Разработка`, чтобы через шаг переименовать; в шаг версии 3 добавлена
|
||||
оговорка «едешь сразу на 4 — заводи `Готово` последней и не переставляй дважды».
|
||||
Лишний шаг — плата за честную историю, и она мала.
|
||||
|
||||
**ЮЮЮ. `Готово` переехало вниз, и порядок секций стал каноническим.**
|
||||
Достигнутое **копится**: через год этой секции больше, чем всех остальных
|
||||
вместе, — и стоя первой она отодвигает за экран ровно то, ради чего роадмап
|
||||
открывают чаще всего. Порядок теперь проверяется (`roadmap_lint`) и правится
|
||||
(`check --fix` переставляет секции вместе с содержимым): без проверки порядок
|
||||
разъедется молча, а переставлять секцию с десятком строк руками — работа, на
|
||||
которой ошибаются.
|
||||
|
||||
**ЯЯЯ. Индексы позиций считаются из самого кортежа.** `ACHIEVED` был `0` и стал
|
||||
`3`; хардкод индексов пережил бы перестановку молча и сломал бы `close`. Теперь
|
||||
`PLANNED, DIRECTIONS, OPERATIONS, ACHIEVED = range(len(ROADMAP_SECTIONS))` —
|
||||
переставили секцию, индексы переехали сами.
|
||||
|
||||
### Что из этого следует
|
||||
|
||||
98. **Отбивка нужна и перед заголовком.** Перестановка блоков ставит два
|
||||
заголовка вплотную — `spaced_sections` правил только строку после. Дефект
|
||||
нашёлся сразу же, на первой перестановке демо-набора: класс правки,
|
||||
существующий только потому, что появилась другая правка.
|
||||
99. **`check --fix` переставляет, но не переименовывает.** Чужую секцию он
|
||||
оставляет ошибкой, и на переименовании `Разработка` → `Сопровождение`
|
||||
останавливается: имя — решение человека, порядок — механика. Тот же разрез,
|
||||
что между регистром (правит) и составом (не трогает).
|
||||
100. **Версия канона отделяет состояния проектов, а не редакции текста** — и
|
||||
ровно поэтому её нельзя не поднять, когда состояние хоть одного проекта
|
||||
уже зафиксировано.
|
||||
|
||||
@@ -116,9 +116,10 @@
|
||||
- [ ] `canon adopt`; `docs/backlog/` → `docs/tasks/`
|
||||
- [ ] `architecture.md` 1662 строки → обзор, остаток маркерами (W)
|
||||
- [ ] после выноса поведения — замерить остаток `architecture.md` и решить по
|
||||
каталожной форме: жмёт → канон версии **4** для `architecture.md` и
|
||||
`review.md`, точка входа `README.md` (тема 16, GGG, 65; версию 3 занял
|
||||
роадмап с родом работы, тема 17, 68)
|
||||
каталожной форме: жмёт → **следующая** версия канона для
|
||||
`architecture.md` и `review.md`, точка входа `README.md` (тема 16, GGG,
|
||||
65; версию 3 занял роадмап с родом работы, тема 17, 68; версию 4 —
|
||||
секция `Сопровождение` и порядок секций, тема 26)
|
||||
- [ ] завести `security.md` с периметром первой строкой (J)
|
||||
- [ ] `review-journal.md` → `review.md` + настройка конвейера (K, L)
|
||||
- [ ] `conventions.md` → `conventions/`, `local-research.md` → `research/` (G)
|
||||
@@ -162,7 +163,8 @@
|
||||
jellybit 43. Шаги повышения — [changelog.md](av-dev-pm/skills/canon/references/changelog.md),
|
||||
запись «Версия 3»; делаются скиллом `av-dev-pm:canon` в режиме `upgrade`.
|
||||
|
||||
- [ ] healthlog: `PLAN.md` → `ROADMAP.md`, ссылки, `"canon": 3`
|
||||
- [x] healthlog: `PLAN.md` → `ROADMAP.md`, ссылки, `"canon": 3` — сделано,
|
||||
лежит в рабочем дереве проекта некоммитнутым
|
||||
- [ ] jellybit: то же
|
||||
- [ ] род работы и раздел «Затрагивает» — **не задним числом**: сперва то, что
|
||||
идёт в ближайший набор (`sprint take` без них откажет), остальное по ходу
|
||||
@@ -181,3 +183,11 @@ jellybit 43. Шаги повышения — [changelog.md](av-dev-pm/skills/can
|
||||
- [ ] заголовки задач в форму действия — **не задним числом**: по мере попадания
|
||||
задачи в работу. `check` печатает их число, `task-form` предложит
|
||||
формулировки пачкой (тема 20, ЕЕЕ)
|
||||
|
||||
**Канон 4** — сверх того (changelog, запись «Версия 4»):
|
||||
|
||||
- [ ] healthlog: `## Разработка` → `## Сопровождение`, поле «Секция» в целях этой
|
||||
секции, `check --fix` (переставит `Готово` вниз и поправит отбивку),
|
||||
`"canon": 4`
|
||||
- [ ] jellybit едет сразу на 4: `Готово` заводить **последней**, секцию
|
||||
сопровождения — сразу с новым именем, переставлять дважды не нужно
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Канон документов проекта
|
||||
|
||||
**Версия 3.**
|
||||
**Версия 4.**
|
||||
|
||||
Это **единственный дом определения канона**. Скиллы `init`, `canon` и `docs`
|
||||
читают его, а не пересказывают: три описания одной раскладки разъедутся, и
|
||||
@@ -350,7 +350,7 @@ kebab-case.
|
||||
|
||||
```json
|
||||
{
|
||||
"canon": 2,
|
||||
"canon": 4,
|
||||
"migrations": "internal/store/migrations",
|
||||
"tasks": {
|
||||
"backlog": "INDEX.md"
|
||||
|
||||
@@ -13,6 +13,61 @@ upgrade` идёт по записям снизу вверх от версии п
|
||||
|
||||
---
|
||||
|
||||
## Версия 4 — 2026-08-04
|
||||
|
||||
Одна секция роадмапа переименована, и вместе с именем расширен её смысл;
|
||||
достигнутое переехало вниз. Плюс общий словарь для трёх мест канона, которые
|
||||
говорят про одну тему разными словами. Раскладка не меняется, файлов не
|
||||
прибавляется.
|
||||
|
||||
**Что переехало:** секция роадмапа `Разработка` → **`Сопровождение`** (англ.
|
||||
`Tooling` → **`Operations`**). Прежнее имя называло слишком много: роадмап
|
||||
**весь** про разработку, и секция с таким именем не отличалась от остальных
|
||||
ничем.
|
||||
|
||||
**Что добавилось:**
|
||||
|
||||
1. **Смысл секции расширен.** Было «инструмент и процесс», стало «чем держат
|
||||
проект: инструмент, процесс, эксплуатация». Метрики, логи, инфраструктура,
|
||||
выкладка и дежурство — сюда же. Расширение не косметическое: английское
|
||||
`Operations` при узком смысле обещало бы эксплуатацию, а внутри лежал бы
|
||||
линтер.
|
||||
2. **Общий словарь трёх мест** — [canon.md](canon.md), раздел «Сопровождение и
|
||||
эксплуатация». Сопровождение — всё, чем держат проект; эксплуатация — его
|
||||
часть, работа системы на проде. `ROADMAP.md`, секция `Сопровождение` — план
|
||||
работ; `architecture.md`, раздел «Эксплуатация» — как устроено сейчас;
|
||||
эксплуатационный проход ревью — оптика проверки. Слить их в одно слово
|
||||
нельзя: они отвечают на разные вопросы. Слово **«поддержка» не употребляется
|
||||
вовсе** — в нём слышится помощь пользователю.
|
||||
3. **Граница с возможностями проходит по тому, кто наблюдает.** «Приложение
|
||||
сообщает о своём состоянии» — возможность приложения, её место среди прочих
|
||||
целей. «Дежурный видит состояние на одном экране» — сопровождение. Одни и те
|
||||
же метрики попадают в разные секции роадмапа, и это верно.
|
||||
4. **Порядок секций стал каноническим**, и `Готово` переехало **вниз**:
|
||||
`Запланировано` | `Направления` | `Сопровождение` | `Готово`. Достигнутое
|
||||
копится — через год этой секции больше, чем всех остальных вместе, — и стоя
|
||||
первой она отодвигает за экран то, ради чего роадмап открывают чаще всего.
|
||||
Порядок проверяет `tasks.py check`, переставляет `check --fix`.
|
||||
5. **Заголовок секции отбивается пустой строкой с обеих сторон.** Прежде
|
||||
проверялась только строка после заголовка; перестановка секций двигает целые
|
||||
блоки, и два заголовка оказываются вплотную. Правит `check --fix`.
|
||||
|
||||
**Что сделать проекту:**
|
||||
|
||||
1. Переименовать заголовок секции в `docs/tasks/ROADMAP.md`: `## Разработка` →
|
||||
`## Сопровождение` (или `## Tooling` → `## Operations`, если индекс
|
||||
английский). **`check --fix` этого не сделает**: регистр канонической секции
|
||||
он правит сам, а чужую секцию только называет ошибкой — смысл за человеком.
|
||||
2. Поправить поле `- **Секция:**` в файлах целей, которые в ней лежат. Порядок
|
||||
именно такой: сперва заголовок, потом `python3 tasks.py check --dir
|
||||
docs/tasks` покажет расхождение поимённо.
|
||||
3. Перечитать состав секции: цели про выкладку, метрики, логи и инфраструктуру,
|
||||
если они лежали в `Направлениях` за неимением места, переезжают сюда.
|
||||
4. Прогнать `python3 tasks.py check --dir docs/tasks --fix`: он переставит
|
||||
секции роадмапа в канонический порядок (`Готово` уедет вниз вместе со всем
|
||||
содержимым) и поправит отбивку заголовков.
|
||||
5. `docs/.pm.json`: `"canon": 4`.
|
||||
|
||||
## Версия 3 — 2026-08-04
|
||||
|
||||
Роадмап стал **состоянием проекта**, а не очередью работ: цель — возможность
|
||||
@@ -34,9 +89,8 @@ upgrade` идёт по записям снизу вверх от версии п
|
||||
3. **Секции роадмапа** — четыре вместо двух и **канонические**, в отличие от
|
||||
секций беклога: `Готово` (достигнутые цели строкой с датой, без ссылки на
|
||||
файл), `Запланировано` (очередь значима), `Направления` (очереди нет),
|
||||
`Сопровождение` (чем держат проект: инструмент, процесс, эксплуатация —
|
||||
не возможности приложения). Английский
|
||||
вариант — `Done` | `Planned` | `Directions` | `Operations`, один язык на весь
|
||||
`Разработка` (инструмент и процесс, не возможности приложения). Английский
|
||||
вариант — `Done` | `Planned` | `Directions` | `Tooling`, один язык на весь
|
||||
индекс. Переименованию проектом не подлежат: у каждой свой смысл, и в первую
|
||||
пишет сам `close`; `tasks.py check` проверяет состав.
|
||||
4. **Форма заголовка записи** — по типу: задача отвечает на «что нужно сделать»
|
||||
@@ -93,7 +147,9 @@ upgrade` идёт по записям снизу вверх от версии п
|
||||
идентичности**, — и убрать остальное, иначе проект возвращает себе прежнюю
|
||||
частоту полного набора уточнением.
|
||||
7. Переименовать секции роадмапа: `порядок` → `Запланировано`, `темы` →
|
||||
`Направления`; завести `Готово` **первой** и `Сопровождение` последней.
|
||||
`Направления`; завести `Готово` **первой** и `Разработка` последней
|
||||
(порядок секций поменялся в версии 4 — если едешь сразу на неё, заводи
|
||||
`Готово` последней и не переставляй дважды).
|
||||
Прозаические разделы вроде «Что уже пройдено», которые велись руками,
|
||||
разложить: звенья — строками в `Готово` (дата, слаг, что стало возможно),
|
||||
обоснование очереди оставить прозой в `Запланировано`. Любой `##` в индексе
|
||||
@@ -101,7 +157,7 @@ upgrade` идёт по записям снизу вверх от версии п
|
||||
8. Переформулировать цели ответом на **«что приложение будет уметь»**: не
|
||||
«Работа со слиянием», а «Исход слияния не зависит от порядка доставки».
|
||||
Свойство поведения — законная цель. Цель, которая не про приложение
|
||||
(процесс, инструмент, эксплуатация), переезжает в `Сопровождение`.
|
||||
(процесс, инструмент), переезжает в `Разработка`.
|
||||
9. `[epic]`, если он в проекте заводился: это либо цель, либо набор задач под
|
||||
общей целью. `check` назовёт его неизвестным типом.
|
||||
10. Прогнать `python3 tasks.py check --dir docs/tasks --fix`: он поднимет
|
||||
|
||||
@@ -392,7 +392,7 @@ severity стоит здесь, а не выводится каждым прох
|
||||
|
||||
```json
|
||||
{
|
||||
"canon": 2
|
||||
"canon": 4
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import NoReturn
|
||||
|
||||
CANON_VERSION = 3
|
||||
CANON_VERSION = 4
|
||||
|
||||
OK, DRIFT, USAGE, ENV, INTERNAL = 0, 1, 2, 3, 4
|
||||
|
||||
|
||||
@@ -67,30 +67,36 @@ docs/tasks/
|
||||
подо что берут.** Цель в спринт взять нельзя, поэтому в списке берущихся ей не
|
||||
место.
|
||||
|
||||
**Четыре секции роадмапа, и первая отвечает на половину вопроса:**
|
||||
**Четыре секции роадмапа, и последняя отвечает на половину вопроса:**
|
||||
|
||||
| Секция | Англ. | Что в ней |
|
||||
| --- | --- | --- |
|
||||
| `Готово` | `Done` | достигнутые цели — строкой с датой, **без ссылки на файл**: файл удалён, поведение живёт в спеках |
|
||||
| `Запланировано` | `Planned` | очередь значима и обосновывается прозой рядом |
|
||||
| `Направления` | `Directions` | очереди нет, тянутся долго |
|
||||
| `Сопровождение` | `Operations` | чем держат проект: инструмент, процесс, эксплуатация — не возможности приложения, и потому отдельно |
|
||||
| `Готово` | `Done` | достигнутые цели — строкой с датой, **без ссылки на файл**: файл удалён, поведение живёт в спеках |
|
||||
|
||||
**Порядок тоже канонический, и `Готово` стоит последним не из скромности.**
|
||||
Достигнутое **копится**: через год этой секции больше, чем всех остальных
|
||||
вместе. Стоя первой, она отодвигает за экран ровно то, ради чего роадмап
|
||||
открывают чаще всего, — что делается сейчас и что дальше. Порядок проверяет
|
||||
`check`, переставляет `check --fix`.
|
||||
|
||||
**Секции роадмапа канонические, секции беклога — нет**, и разница не в любви к
|
||||
единообразию. У каждой секции роадмапа свой смысл, в первую пишет сам `close`, и
|
||||
единообразию. У каждой секции роадмапа свой смысл, в достигнутое пишет сам `close`, и
|
||||
роадмап, названный по-своему, читался бы только своим автором. Секции беклога
|
||||
(`Ядро`, `Инфра`) смысла не несут — это полки, и остаются делом проекта.
|
||||
|
||||
Отсюда три правила, которые проверяет `tasks.py check`: **состав закреплён**
|
||||
Отсюда четыре правила, которые проверяет `tasks.py check`: **состав закреплён**
|
||||
(чужая секция — ошибка, а не вольность), **все четыре обязаны быть** (нет
|
||||
секции — нет ответа на её часть вопроса), **язык один на весь индекс**.
|
||||
`--roadmap-sections` у `init` нет: выбирать нечего.
|
||||
секции — нет ответа на её часть вопроса), **язык один на весь индекс**, **порядок
|
||||
канонический**. `--roadmap-sections` у `init` нет: выбирать нечего.
|
||||
|
||||
**Заголовок секции — с прописной, после него пустая строка.** Во всех индексах
|
||||
одинаково, включая секции беклога, которые проект называет сам. Написание
|
||||
канонических секций правит `check --fix` (заодно и ссылку на секцию в мете
|
||||
файлов: имя секции принадлежит заголовку индекса, файл на неё только
|
||||
ссылается); отбивку он ставит везде.
|
||||
**Заголовок секции отбит пустой строкой с обеих сторон и написан с прописной.**
|
||||
Во всех индексах одинаково, включая секции беклога, которые проект называет сам.
|
||||
Написание канонических секций правит `check --fix` (заодно и ссылку на секцию в
|
||||
мете файлов: имя секции принадлежит заголовку индекса, файл на неё только
|
||||
ссылается); отбивку и порядок он правит везде.
|
||||
|
||||
Оговорка про `Сопровождение`: слово `окружение` сюда не годится — в
|
||||
`architecture.md` оно уже значит боевое окружение приложения, и одно слово в двух
|
||||
|
||||
@@ -225,7 +225,7 @@
|
||||
|
||||
| Файл | Что отвечает | Секции |
|
||||
| --- | --- | --- |
|
||||
| `ROADMAP.md` | что приложение уже умеет и чего ещё не умеет | канонические: `Готово`, `Запланировано`, `Направления`, `Сопровождение` (англ. `Done`, `Planned`, `Directions`, `Operations`) |
|
||||
| `ROADMAP.md` | что приложение уже умеет и чего ещё не умеет | канонические и в этом порядке: `Запланировано`, `Направления`, `Сопровождение`, `Готово` (англ. `Planned`, `Directions`, `Operations`, `Done`) |
|
||||
| `BACKLOG.md` | что **можно взять** — только задачи | секции проекта (по умолчанию Ядро/Инфра) |
|
||||
| `SPRINT.md` | какая цель и какой набор под неё | одна: «Набор» |
|
||||
| `REJECTED.md` | что ушло без реализации и почему | — |
|
||||
@@ -250,11 +250,13 @@
|
||||
строки не той формы, что у прочих индексов: дата, слаг, заголовок — как в
|
||||
`REJECTED.md`, и по той же причине (файла уже нет, ссылаться некуда).
|
||||
|
||||
**Секции роадмапа закреплены** — состав, полнота и единство языка проверяются
|
||||
`check`; секции беклога проект называет сам. Почему так — SKILL.md.
|
||||
**Секции роадмапа закреплены** — состав, полнота, единство языка и **порядок**
|
||||
проверяются `check`; секции беклога проект называет сам. Почему так — SKILL.md.
|
||||
Порядок закреплён потому, что `Готово` копится: стоя первым, достигнутое
|
||||
отодвигает за экран то, ради чего роадмап открывают чаще всего.
|
||||
|
||||
**Заголовок секции пишется с прописной, и после него идёт пустая строка** — во
|
||||
всех индексах, включая секции беклога, имена которых выбирает проект. Написание
|
||||
**Заголовок секции пишется с прописной и отбивается пустой строкой с обеих
|
||||
сторон** — во всех индексах, включая секции беклога, имена которых выбирает проект. Написание
|
||||
канонических секций и отбивку правит `check --fix`; он же сводит написание
|
||||
секции в мете файла с заголовком индекса — **имя секции принадлежит заголовку**,
|
||||
файл на неё лишь ссылается, и принадлежность сверяется по нижнему регистру.
|
||||
|
||||
@@ -13,9 +13,9 @@ av-dev, и подгоняется под него проект. Имена вн
|
||||
docs/tasks/
|
||||
items/ задачи и цели файлами, <slug>.md
|
||||
ROADMAP.md состояние проекта: что уже умеет и чего ещё не умеет.
|
||||
Секции канонические: Готово | Запланировано |
|
||||
Направления | Сопровождение (или Done | Planned |
|
||||
Directions | Operations — один язык на весь индекс)
|
||||
Секции канонические и в этом порядке: Запланировано |
|
||||
Направления | Сопровождение | Готово (или Planned |
|
||||
Directions | Operations | Done — один язык на индекс)
|
||||
BACKLOG.md что можно взять — только задачи, целей здесь нет
|
||||
SPRINT.md текущий спринт: цель, набор, дата, слаг
|
||||
REJECTED.md ушедшее БЕЗ реализации, с причиной и датой
|
||||
@@ -131,7 +131,7 @@ DEFAULT_SECTIONS = "Ядро,Инфра"
|
||||
|
||||
# Секции роадмапа **канонические**, в отличие от секций беклога. Причина не в
|
||||
# любви к единообразию: у каждой своя семантика — достигнутое, очередь, долгие
|
||||
# направления, работа над инструментом, — в первую пишет сам `close`, и роадмап,
|
||||
# направления, работа по сопровождению, — в достигнутое пишет сам `close`, и роадмап,
|
||||
# названный по-своему, читался бы только своим автором. Секции беклога семантики
|
||||
# не несут, это полки, и остаются делом проекта.
|
||||
#
|
||||
@@ -139,13 +139,17 @@ DEFAULT_SECTIONS = "Ядро,Инфра"
|
||||
# индекс** — вперемешку это дрейф, который check называет вслух. Сверка везде
|
||||
# идёт по нижнему регистру, а пишется — как здесь: заголовок предложением, с
|
||||
# прописной.
|
||||
# Порядок значим и проверяется: достигнутое **копится**, и стоя первым оно со
|
||||
# временем отодвигает за экран всё, ради чего роадмап открывают.
|
||||
ROADMAP_SECTIONS = (
|
||||
("Готово", "Done"), # достигнутое: что приложение уже умеет
|
||||
("Запланировано", "Planned"), # очередь значима, обоснована прозой
|
||||
("Направления", "Directions"), # очереди нет, тянутся долго
|
||||
("Сопровождение", "Operations"), # чем держат проект, а не что умеет приложение
|
||||
("Готово", "Done"), # достигнутое: что приложение уже умеет
|
||||
)
|
||||
ACHIEVED, PLANNED = 0, 1 # индексы в ROADMAP_SECTIONS
|
||||
# Позиции — из самого кортежа, а не числами: переставили секцию — индексы
|
||||
# переехали сами.
|
||||
PLANNED, DIRECTIONS, OPERATIONS, ACHIEVED = range(len(ROADMAP_SECTIONS))
|
||||
DEFAULT_ROADMAP_SECTIONS = ",".join(ru for ru, _ in ROADMAP_SECTIONS)
|
||||
|
||||
# Мета — список под заголовком, поле на строку. Старая форма (все поля одной
|
||||
@@ -568,17 +572,28 @@ def action_title(title: str) -> bool:
|
||||
|
||||
|
||||
def spaced_sections(lines: list[str]) -> list[str]:
|
||||
"""Отбивка после заголовка секции. Заголовок, пустая строка, потом
|
||||
содержимое — во всех индексах одинаково.
|
||||
"""Отбивка вокруг заголовка секции: пустая строка перед ним и после него.
|
||||
|
||||
Живёт на записи, а не на вставке: через `Plan.index` проходит **каждая**
|
||||
запись индекса, и чинить отбивку в каждом месте вставки значило бы
|
||||
полагаться на то, что ни одно из них не забыли."""
|
||||
полагаться на то, что ни одно из них не забыли. Перед заголовком — не
|
||||
педантизм: перестановка секций двигает целые блоки, и два заголовка легко
|
||||
оказываются вплотную друг к другу.
|
||||
|
||||
Заодно схлопывает подряд идущие пустые строки: удаление строки индекса
|
||||
оставляет после себя две, и без этого шага пустоты копятся."""
|
||||
out: list[str] = []
|
||||
for i, line in enumerate(lines):
|
||||
if SECTION.match(line):
|
||||
if out and out[-1].strip():
|
||||
out.append("")
|
||||
out.append(line)
|
||||
if i + 1 < len(lines) and lines[i + 1].strip():
|
||||
out.append("")
|
||||
continue
|
||||
if not line.strip() and out and not out[-1].strip():
|
||||
continue
|
||||
out.append(line)
|
||||
if SECTION.match(line) and i + 1 < len(lines) and lines[i + 1].strip():
|
||||
out.append("")
|
||||
return out
|
||||
|
||||
|
||||
@@ -591,9 +606,6 @@ def index_lint(lines: list[str], label: str) -> list[str]:
|
||||
for num, line in enumerate(lines, 1):
|
||||
if (m := SECTION.match(line)):
|
||||
section = m.group(1)
|
||||
if num < len(lines) and lines[num].strip():
|
||||
errors.append(f"{label}:{num}: после заголовка «{section}» нет"
|
||||
f" пустой строки; починит `check --fix`")
|
||||
continue
|
||||
if not line.startswith("- ["):
|
||||
continue
|
||||
@@ -610,6 +622,13 @@ def index_lint(lines: list[str], label: str) -> list[str]:
|
||||
f" (первая — строка {seen[target]})")
|
||||
else:
|
||||
seen[target] = num
|
||||
# Оформление сверяется **самим нормализатором**, а не своим набором условий:
|
||||
# два описания одного правила разъедутся, и `check` начнёт молчать о том,
|
||||
# что `--fix` правит (или наоборот).
|
||||
if spaced_sections(lines) != lines:
|
||||
errors.append(f"{label}: оформление секций — заголовок отбивается пустой"
|
||||
f" строкой с обеих сторон, подряд идущих пустых строк не"
|
||||
f" бывает; починит `check --fix`")
|
||||
return errors
|
||||
|
||||
|
||||
@@ -1218,8 +1237,34 @@ def canon_section(lines: list[str], which: int) -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
def roadmap_ordered(lines: list[str]) -> list[str]:
|
||||
"""Секции роадмапа, переставленные в канонический порядок вместе с их
|
||||
содержимым. Преамбула остаётся на месте.
|
||||
|
||||
Чужая секция останавливает перестановку целиком: её место в порядке
|
||||
неизвестно, а угадывать значило бы переложить чьи-то строки наугад. О ней
|
||||
скажет `roadmap_lint`, и человек решит сам."""
|
||||
heads = [(i, m.group(1)) for i, line in enumerate(lines) if (m := SECTION.match(line))]
|
||||
if not heads:
|
||||
return lines
|
||||
order = {n.lower(): i for i, pair in enumerate(ROADMAP_SECTIONS) for n in pair}
|
||||
if any(name.lower() not in order for _, name in heads):
|
||||
return lines
|
||||
blocks = []
|
||||
for k, (i, name) in enumerate(heads):
|
||||
end = heads[k + 1][0] if k + 1 < len(heads) else len(lines)
|
||||
blocks.append((order[name.lower()], lines[i:end]))
|
||||
if [rank for rank, _ in blocks] == sorted(rank for rank, _ in blocks):
|
||||
return lines
|
||||
out = lines[:heads[0][0]]
|
||||
for _, block in sorted(blocks, key=lambda b: b[0]):
|
||||
out = out + block
|
||||
return out
|
||||
|
||||
|
||||
def roadmap_lint(lines: list[str], label: str) -> list[str]:
|
||||
"""Секции роадмапа: все канонические, все на месте, все на одном языке."""
|
||||
"""Секции роадмапа: все канонические, все на месте, все на одном языке и в
|
||||
каноническом порядке."""
|
||||
known = {n.lower(): i for i, pair in enumerate(ROADMAP_SECTIONS) for n in pair}
|
||||
errors: list[str] = []
|
||||
seen: dict[int, str] = {}
|
||||
@@ -1251,6 +1296,12 @@ def roadmap_lint(lines: list[str], label: str) -> list[str]:
|
||||
f" целиком — отсутствующая секция это отсутствующий ответ")
|
||||
if len(langs) > 1:
|
||||
errors.append(f"{label}: секции вперемешку на двух языках — выбери один")
|
||||
if not missing and roadmap_ordered(lines) != lines:
|
||||
canon = ", ".join(pair[0] for pair in ROADMAP_SECTIONS)
|
||||
errors.append(f"{label}: секции не в каноническом порядке ({canon}) —"
|
||||
f" достигнутое копится и потому стоит последним, иначе оно"
|
||||
f" отодвигает за экран то, ради чего роадмап открывают;"
|
||||
f" переставит `check --fix`")
|
||||
return errors
|
||||
|
||||
|
||||
@@ -2199,6 +2250,11 @@ def apply_fixes(lay: Layout) -> tuple[list[str], list[str]]:
|
||||
lines[j] = f"## {want}"
|
||||
fixed.append(f"{lay.name(kind)}: секция «{m.group(1)}» → «{want}»")
|
||||
dirty.add(kind)
|
||||
if (moved := roadmap_ordered(lines)) != lines:
|
||||
lines[:] = moved
|
||||
fixed.append(f"{lay.name(kind)}: секции переставлены в канонический"
|
||||
f" порядок ({', '.join(p[0] for p in ROADMAP_SECTIONS)})")
|
||||
dirty.add(kind)
|
||||
if spaced_sections(lines) != lines:
|
||||
fixed.append(f"{lay.name(kind)}: отбивка после заголовков секций")
|
||||
dirty.add(kind)
|
||||
@@ -2272,16 +2328,17 @@ def init_files(lay: Layout, sections: list[str], roadmap_sections: list[str],
|
||||
f"Цель — возможность приложения, файл `[goal]` в `{lay.cfg['items']}/`; её\n"
|
||||
"задачи здесь **не перечисляются** — перечень даёт\n"
|
||||
"`tasks.py list --goal <слаг>`.\n\n"
|
||||
f"- **{ROADMAP_SECTIONS[PLANNED][0]}** — очередь значима и обосновывается прозой;\n"
|
||||
f"- **{ROADMAP_SECTIONS[DIRECTIONS][0]}** — очереди нет, тянутся долго;\n"
|
||||
f"- **{ROADMAP_SECTIONS[OPERATIONS][0]}** — чем держат проект: инструмент,\n"
|
||||
" процесс, эксплуатация. Не возможности приложения, и отдельно —\n"
|
||||
" чтобы не читаться как обещание продукта;\n"
|
||||
f"- **{ROADMAP_SECTIONS[ACHIEVED][0]}** — достигнутое: строку пишет\n"
|
||||
" `tasks.py close <цель> --implemented`, ссылки на файл в ней нет —\n"
|
||||
" файл удаляется, поведение живёт в спеках;\n"
|
||||
f"- **{ROADMAP_SECTIONS[PLANNED][0]}** — очередь значима и обосновывается прозой;\n"
|
||||
f"- **{ROADMAP_SECTIONS[2][0]}** — очереди нет, тянутся долго;\n"
|
||||
f"- **{ROADMAP_SECTIONS[3][0]}** — чем держат проект: инструмент,\n"
|
||||
" процесс, эксплуатация. Не возможности приложения, и отдельно —\n"
|
||||
" чтобы не читаться как обещание продукта.\n\n"
|
||||
" файл удаляется, поведение живёт в спеках. Стоит последней: копится.\n\n"
|
||||
"Секции **канонические** и переименованию проектом не подлежат:\n"
|
||||
"у каждой свой смысл, и в первую пишет сам `close`. Английский\n"
|
||||
"у каждой свой смысл, и в достигнутое пишет сам `close`. Порядок\n"
|
||||
"тоже канонический. Английский\n"
|
||||
f"вариант — {' | '.join(pair[1] for pair in ROADMAP_SECTIONS)},"
|
||||
" один язык на весь\nиндекс.\n\n"
|
||||
+ "".join(f"## {s}\n\n" for s in roadmap_sections))
|
||||
|
||||
Reference in New Issue
Block a user