diff --git a/av-dev-pm/skills/tasks/references/task-format.md b/av-dev-pm/skills/tasks/references/task-format.md index fee4841..3b9e8d7 100644 --- a/av-dev-pm/skills/tasks/references/task-format.md +++ b/av-dev-pm/skills/tasks/references/task-format.md @@ -172,6 +172,12 @@ | `SPRINT.md` | какая цель и какой набор под неё | одна: «Набор» | | `REJECTED.md` | что ушло без реализации и почему | — | +Шапку `SPRINT.md` пишет `sprint start` — **тем же мета-блоком, что у задачи**: +поле на строку, `- **Цель:** [Заголовок](items/slug.md)`, `- **Начат:**` датой, +`- **Спринт:**` слагом, которым метится урожай. Прежняя форма (три поля одной +строкой через `·`) читается по-прежнему и уходит сама: файл переписывается на +следующем `sprint start` и очищается на `sprint close`. + Секции — **единственные заголовки `##` в индексе**: любой другой `##` в преамбуле проверка сочтёт секцией. Внутри секции беклога порядок значения не имеет — порядка в беклоге нет вовсе. diff --git a/av-dev-pm/skills/tasks/scripts/tasks.py b/av-dev-pm/skills/tasks/scripts/tasks.py index 429d5e9..8847efd 100755 --- a/av-dev-pm/skills/tasks/scripts/tasks.py +++ b/av-dev-pm/skills/tasks/scripts/tasks.py @@ -161,7 +161,9 @@ 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"^\*\*(?:Цель|Goal):\*\*\s*\[(.+?)\]\((.+?\.md)\)") +GOAL_LINE = re.compile(r"^(?:-\s+)?\*\*(?:Цель|Goal):\*\*\s*\[(.+?)\]\((.+?\.md)\)") +# `search`, а не `match`: строка меты идёт пунктом списка, а прежняя форма — +# третьим полем строки через `·`. Обе читаются, пишется новая. SPRINT_SLUG_LINE = re.compile(r"\*\*(?:Спринт|Sprint):\*\*\s*`?([a-z0-9][a-z0-9.-]*)`?") DATE_RE = re.compile(r"\d{4}-\d{2}-\d{2}") BULLET = re.compile(r"^[-*]\s+(.*)$") @@ -651,10 +653,18 @@ def sprint_slug(lay: Layout) -> str: def sprint_header(lay: Layout, goal_slug: str, goal_title: str, date: str, slug: str) -> list[str]: + """Шапка спринта — мета-блок той же формы, что у задачи: поле на строку. + + Прежняя форма (все три поля одной строкой через `·`) читается по-прежнему + — обе регулярки её берут, — но чинить её нечем и не нужно: `SPRINT.md` + переписывается целиком на `sprint start` и очищается на `sprint close`, + так что старая шапка живёт не дольше идущего спринта. + """ link = f"{lay.cfg['items']}/{goal_slug}.md" return ["# Спринт", "", - f"**Цель:** [{goal_title}]({link}) · **Начат:** {date}" - f" · **Спринт:** `{slug}`", "", + f"- **Цель:** [{goal_title}]({link})", + f"- **Начат:** {date}", + f"- **Спринт:** `{slug}`", "", f"Урожай спринта поднимается `tasks.py list --tag {SPRINT_TAG}{slug}`" " — это первая порция переоценки на сессии.", ""] @@ -837,7 +847,7 @@ def check(lay: Layout, fix: bool = False) -> int: if not goal_of_sprint and entries["sprint"]: errors.append(f"{label['sprint']}: набор есть, а цель не названа") if goal_of_sprint and not sprint_slug(lay): - errors.append(f"{label['sprint']}: у спринта нет слага (**Спринт:** `…`) —" + errors.append(f"{label['sprint']}: у спринта нет слага (- **Спринт:** `…`) —" f" урожай не отобрать; перезапусти `sprint start`") rejected = lay.index("rejected")