From 324fb289ab1c161d5343f1b96614c372d1b4bd35 Mon Sep 17 00:00:00 2001 From: Anton Vakhrushev Date: Mon, 3 Aug 2026 18:04:04 +0300 Subject: [PATCH] =?UTF-8?q?=D1=81=D0=BF=D1=80=D0=B8=D0=BD=D1=82:=20=D1=88?= =?UTF-8?q?=D0=B0=D0=BF=D0=BA=D0=B0=20SPRINT.md=20=E2=80=94=20=D0=BC=D0=B5?= =?UTF-8?q?=D1=82=D0=B0-=D0=B1=D0=BB=D0=BE=D0=BA=D0=BE=D0=BC,=20=D0=BA?= =?UTF-8?q?=D0=B0=D0=BA=20=D1=83=20=D0=B7=D0=B0=D0=B4=D0=B0=D1=87=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Последнее место, где поля писались одной строкой через ·: цель, дата начала и слаг спринта. После переезда меты задачи в блок эта строка осталась единственным исключением, а объяснять два формата дороже, чем иметь один. Старая шапка читается по-прежнему — GOAL_LINE берёт строку и с ведущим «- », SPRINT_SLUG_LINE и раньше искала по всей строке. Починки для неё нет и не нужно: SPRINT.md переписывается целиком на sprint start и очищается на sprint close, так что старая форма живёт не дольше идущего спринта. Форма заодно описана в task-format.md — до сих пор она жила только в коде, и человек, читавший документ формата, о ней не узнавал. Проверено на временном проекте: start → take → check → подсунутая старая шапка → drop → close; цель и слаг читаются в обеих формах. Co-Authored-By: Claude Opus 5 (1M context) --- .../skills/tasks/references/task-format.md | 6 ++++++ av-dev-pm/skills/tasks/scripts/tasks.py | 18 ++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) 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")