From 1f31ac6afdce20e88b71edac663a365c2e7d789b Mon Sep 17 00:00:00 2001 From: Anton Vakhrushev Date: Sun, 9 Aug 2026 14:12:08 +0300 Subject: [PATCH] =?UTF-8?q?=D0=BA=D0=B0=D0=BD=D0=BE=D0=BD=20=D0=BE=D1=82?= =?UTF-8?q?=D0=BF=D1=83=D1=81=D1=82=D0=B8=D0=BB=20=D0=BA=D0=B0=D1=82=D0=B0?= =?UTF-8?q?=D0=BB=D0=BE=D0=B3=20=D0=B7=D0=B0=D0=B4=D0=B0=D1=87:=20docs.py?= =?UTF-8?q?=20=D0=BD=D0=B5=20=D0=B7=D0=BE=D0=B2=D1=91=D1=82=20tasks.py,=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BD=D1=84=D0=B8=D0=B3=20=D1=80=D0=B0=D0=B7=D1=8A?= =?UTF-8?q?=D0=B5=D1=85=D0=B0=D0=BB=D1=81=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Пока владелец был один, docs/tasks/ числился слотом канона: docs.py требовал каталог, звал внутрь чужой скрипт подпроцессом и выдавал его дрейф за свой, а настройки задач жили ключом tasks в docs/.pm.json. Для проекта, поставившего только документы, всё это отказ на ровном месте — задач он не ведёт, и требовать их не за что. Раскол вскрыл это немедленно и молча: check_tasks искал tasks.py по пути parents[2]/tasks/scripts, то есть внутри своего плагина, и после переезда скатывался в ветку «скрипт не найден» на каждом прогоне. Проверка выглядела живой и не проверяла ничего. Теперь docs.py про задачи не говорит ни слова: check_tasks снят целиком, каталог остаётся в NOT_DOCS, его отсутствие дрейфом не считается. Канон резервирует место в docs/ и внутрь не смотрит. Дом настроек каталога задач вернулся в свой файл — <каталог>/.tasks.json. Прежний ключ tasks в docs/.pm.json читается, только когда своего файла нет, и скрипт говорит, куда его перенести; есть оба — побеждает свой, и об этом тоже говорится вслух. Порядок именно такой, потому что docs/ принадлежит другому плагину: дом настроек в чужом дереве это дом, которого у половины проектов нет. Заодно закрыта дыра, которую сам же и открыл первый вариант правки: битый docs/.pm.json ронял бы задачи даже при живом своём конфиге. Чужой файл здесь только повод для замечания, и его поломка не наша. Проверено на четырёх случаях — только чужой конфиг, оба, свой плюс битый чужой (код 0), только битый чужой (код 3, окружение). Канон повышен до версии 8 с записью, выполнимой upgrade. В ней названо и то, что легко потерять: раньше согласованность задач тянул за собой docs.py check, и проект, у которого в гейте стоял только он, обязан добавить второй шаг — иначе дрейф индексов перестанет ловиться молча. Гейт зелёный. Оба скрипта прогнаны: docs.py check на фикстуре про задачи не упоминает, tasks.py check код 0. Co-Authored-By: Claude Opus 5 (1M context) --- av-dev-docs/skills/canon/SKILL.md | 15 ++-- av-dev-docs/skills/canon/references/canon.md | 47 ++++++----- .../skills/canon/references/changelog.md | 34 ++++++++ .../skills/canon/references/skeletons.md | 9 +-- av-dev-docs/skills/canon/scripts/docs.py | 40 ++------- av-dev-tasks/skills/tasks/SKILL.md | 15 +++- av-dev-tasks/skills/tasks/scripts/tasks.py | 81 ++++++++++--------- 7 files changed, 131 insertions(+), 110 deletions(-) diff --git a/av-dev-docs/skills/canon/SKILL.md b/av-dev-docs/skills/canon/SKILL.md index d276202..7fe35f9 100644 --- a/av-dev-docs/skills/canon/SKILL.md +++ b/av-dev-docs/skills/canon/SKILL.md @@ -170,13 +170,14 @@ capability), `openspec/config.yaml`. 9. `docs.py check` — до **отсутствия дрейфа раскладки**. Замечания (незаполненные плейсхолдеры, слабое упоминание capability) остаются: незаполненный канон это объявленное переходное состояние из шага 5, а не - отказ. **Пункт «задачи без цели» из вложенной проверки `tasks.py` тоже - остаётся** и зелёным на этом шаге не станет: цели не сочиняются адаптацией - (запрет записан у того, кто ведёт задачи, — сценарий адаптации скилла - `av-dev-tasks:tasks`), их - проставляет человек порциями переоценки на первой сессии. Пересчитай эти - пункты в докладе переходного состояния — не выдавай их за поломку и не - молчи о них. + отказ. Пересчитай эти пункты в докладе переходного состояния — не выдавай + их за поломку и не молчи о них. + + **Задачи `docs.py` не проверяет** — их ведёт другой плагин, и согласованность + каталога показывает только `tasks.py check`. Позвал на шаге 5 скилл задач — + его отчёт идёт в доклад отдельной строкой, и пункт «задачи без цели» в нём + зелёным не станет: цели не сочиняются адаптацией (запрет записан у того, кто + ведёт задачи), их проставляет человек порциями переоценки на первой сессии. ### 5. Объяви переходное состояние diff --git a/av-dev-docs/skills/canon/references/canon.md b/av-dev-docs/skills/canon/references/canon.md index dcd77ed..052ee66 100644 --- a/av-dev-docs/skills/canon/references/canon.md +++ b/av-dev-docs/skills/canon/references/canon.md @@ -75,8 +75,8 @@ docs/ adr.md | adr/ почему решено так; статусы, правило замены review.md | review/ настройка конвейера под проект + журнал дефектов <своя тема>.md | <своя тема>/ всё, что проект счёл нужным проверять - tasks/ скилл tasks: items/, ROADMAP.md, BACKLOG.md, - SPRINT.md, REJECTED.md + tasks/ каталог задач — плагин av-dev-tasks, не канон: + место зарезервировано, наличия канон не требует openspec/ config.yaml только нужды генерации артефактов + ссылки specs//spec.md что система делает — нормативно @@ -115,7 +115,7 @@ openspec/ | `CLAUDE.md`, `AGENTS.md` | источник | `autotests` (семантика гейта); инварианты — сквозные | | `openspec/specs/` | источник | `requirements` | | `openspec/config.yaml` | процессный | — (настройка порождения артефактов, слой **до** тем) | -| `tasks/` | процессный | — | +| `tasks/` | процессный | — (чужое владение: плагин `av-dev-tasks`) | | `review.*` | процессный | — (настройка самого конвейера, слой **над** темами) | | `adr.*` | процессный | — | | `research.*` | процессный | — | @@ -353,9 +353,16 @@ kebab-case.** Причина не эстетическая: имя файла с ### `tasks/` -Раскладку, форму записи и команды держит скилл `tasks` — канон фиксирует имена -файлов (`items/`, `ROADMAP.md`, `BACKLOG.md`, `SPRINT.md`, `REJECTED.md`) и то, -от чего зависит, читается ли проект как продукт. +**Каталог задач канону не принадлежит.** Его ведёт отдельный плагин +`av-dev-tasks` — своим скриптом, своим конфигом `<каталог>/.tasks.json` и своей +версией формата. Канон **резервирует место** в `docs/` и внутрь не смотрит: +`docs.py` каталог не открывает, его отсутствия не считает дрейфом и согласованность +задач не проверяет. Проект, поставивший только канон документов, задач не ведёт +вовсе, и отказом это быть не может. + +Раскладку, форму записи и команды держит скилл `av-dev-tasks:tasks`. Ниже — то, +от чего зависит, читается ли проект как продукт: канон высказывается об этом +потому, что роадмап отвечает на вопрос о **системе**, а не о работах. **`ROADMAP.md` отвечает на «что приложение уже умеет и чего ещё не умеет».** Это не очередь работ: цель — **возможность приложения**, задача — шаг к ней. @@ -563,30 +570,22 @@ OpenSpec переименует артефакт или сменит схему ```json { - "canon": 7, - "migrations": "internal/store/migrations", - "tasks": { - "backlog": "INDEX.md" - } + "canon": 8, + "migrations": "internal/store/migrations" } ``` `canon` — версия канона, под которую проект приведён, целым числом: обратной совместимости у канона нет, есть «приведён» и «не приведён». `migrations` — путь каталога миграций, если БД есть; по нему `docs.py` делает сверку с -`database.md`. `tasks` — настройки каталога задач, переехавшие сюда из прежнего -`/.tasks.json`: **один конфиг на весь канон, а не по одному на каталог**. -Внутри `tasks` — **только имена файлов и заголовков** (`items`, `backlog`, -`roadmap`, `sprint`, `rejected`, `sprint_section`, `oracle_word` и заголовки -разделов тела: `criteria_heading`, `surface_heading`, `questions_heading`, -`completion_heading`, `repro_heading`, `question_heading`, `answer_heading`, -`scope_heading`), и ключ пишется, лишь когда имя отличается от умолчания. -**Словаря типов здесь нет** — он закрыт каноном, а не настраивается проектом: -настраиваемый словарь типов разъехался бы на синонимах ровно так же, как -открытый. **Категорий беклога здесь тоже нет:** их дом — заголовки `##` самого -индекса, и второй список сразу разошёлся бы с первым. Неизвестный ключ `tasks.py` -отвергает кодом 3, поэтому лишнее слово в этом объекте останавливает работу с -задачами целиком. +`database.md`. + +**Ключа `tasks` здесь больше нет.** Настройки каталога задач вернулись в свой +файл `<каталог задач>/.tasks.json`, потому что ведёт их другой плагин: конфиг, +лежащий в `docs/`, был бы домом, которого нет у проекта, поставившего учёт работ +без канона документов. Состав ключей описывает тот плагин, а не канон. Прежний +ключ читается, пока живы непереехавшие проекты, и `tasks.py` говорит о нём +замечанием на каждом прогоне — версия 8 журнала просит его убрать. Ключей будет больше по мере роста проверок; неизвестный ключ `docs.py` игнорирует, отсутствующий — считает «проверка неприменима» и говорит об этом diff --git a/av-dev-docs/skills/canon/references/changelog.md b/av-dev-docs/skills/canon/references/changelog.md index 1001379..8d622ef 100644 --- a/av-dev-docs/skills/canon/references/changelog.md +++ b/av-dev-docs/skills/canon/references/changelog.md @@ -13,6 +13,40 @@ upgrade` идёт по записям снизу вверх от версии п --- +## Версия 8 — 2026-08-09 + +Канон отпустил каталог задач. Плагин `av-dev-pm` расколот на `av-dev-docs` +(документы) и `av-dev-tasks` (учёт работ), и каждый теперь ставится сам по себе. +Пока владелец был один, `docs/tasks/` числился слотом канона: `docs.py` требовал +каталог, звал внутрь чужой скрипт и выдавал его дрейф за свой, а настройки задач +жили ключом `tasks` в `docs/.pm.json`. Для проекта, поставившего только документы, +всё это — отказ на ровном месте: задач он не ведёт, и требовать их не за что. + +**Что изменилось.** Каталог задач канону не принадлежит; канон резервирует ему +место в `docs/` и внутрь не смотрит. `docs.py` больше не проверяет согласованность +задач вовсе — это делает `tasks.py` сам, командой своего плагина. Дом настроек +каталога задач — `<каталог задач>/.tasks.json`; ключ `tasks` в `docs/.pm.json` +читается, только пока своего файла нет, и об этом говорится замечанием. + +**Что удалено.** Проверка `check_tasks` из `docs.py` и ключ `"tasks"` из скелета +`docs/.pm.json`. + +**Что сделать проекту.** + +1. Перенести настройки задач: содержимое ключа `"tasks"` из `docs/.pm.json` — в + `docs/tasks/.tasks.json` тем же объектом. Ключа в проекте нет (имена файлов + и заголовков умолчательные) — переносить нечего, шаг пропускается. +2. Удалить ключ `"tasks"` из `docs/.pm.json` после переноса. Оставленный он не + читается, и `tasks.py` скажет об этом замечанием на каждом прогоне. +3. Проверить, что согласованность задач по-прежнему кто-то гоняет: раньше её + тянул за собой `docs.py check`, теперь — только `tasks.py check`. **Если в + гейте проекта стоял один `docs.py`, добавить туда второй шаг** — иначе дрейф + индексов перестанет ловиться молча, и это самая вероятная потеря на этом + повышении. +4. Установить оба плагина, если нужны оба: `av-dev-docs` и `av-dev-tasks` + вместо прежнего `av-dev-pm`. Прежний из `enabledPlugins` убрать. +5. `docs/.pm.json`: `"canon": 8`. + ## Версия 7 — 2026-08-07 `openspec/` был предпосылкой, о которой канон говорил, но за которой не следил. diff --git a/av-dev-docs/skills/canon/references/skeletons.md b/av-dev-docs/skills/canon/references/skeletons.md index 18603a0..e0cd17e 100644 --- a/av-dev-docs/skills/canon/references/skeletons.md +++ b/av-dev-docs/skills/canon/references/skeletons.md @@ -501,11 +501,10 @@ rules: ```json { - "canon": 7 + "canon": 8 } ``` -Плюс `"migrations": "<путь>"`, если есть БД. Ключ `"tasks"` заводится **только** -когда имя файла или заголовка отличается от умолчания (`{"backlog": -"INDEX.md"}`); секций беклога в нём нет — их дом заголовки `##` индекса. Состав -ключей — [canon.md](canon.md). +Плюс `"migrations": "<путь>"`, если есть БД. Ключа `"tasks"` здесь **нет**: +настройки каталога задач переехали в свой файл `<каталог задач>/.tasks.json`, +потому что ведёт их другой плагин. Состав ключей — [canon.md](canon.md). diff --git a/av-dev-docs/skills/canon/scripts/docs.py b/av-dev-docs/skills/canon/scripts/docs.py index 337df0e..8ce55b0 100644 --- a/av-dev-docs/skills/canon/scripts/docs.py +++ b/av-dev-docs/skills/canon/scripts/docs.py @@ -25,7 +25,7 @@ from dataclasses import dataclass, field from pathlib import Path from typing import NoReturn -CANON_VERSION = 7 +CANON_VERSION = 8 OK, DRIFT, USAGE, ENV, INTERNAL = 0, 1, 2, 3, 4 @@ -110,8 +110,12 @@ OPENSPEC_SCHEMA = "spec-driven" # проверка и заведена. OPENSPEC_ARTIFACTS = ("proposal", "specs", "design", "tasks") -# Служебное в docs/ и каталог, который ведёт tasks.py. Оба процессные, но -# проверок формы у них нет: .pm.json не markdown, tasks/ ведёт другой скрипт. +# Служебное в docs/ и каталог задач. Формы у них скрипт не проверяет, и по разным +# причинам: `.pm.json` не markdown, а `tasks/` **принадлежит другому плагину** — +# `av-dev-tasks`, со своим скриптом, своим конфигом и своей версией формата. +# Канон резервирует за ним место в `docs/`, но наличия не требует и внутрь не +# смотрит: проект, поставивший только этот плагин, задач не ведёт вовсе, и +# отказом это быть не может. NOT_DOCS = {".pm.json", "tasks"} # Слоты, которых в каноне нет, — с адресом, куда уезжает содержимое. Имена, @@ -722,35 +726,6 @@ def check_migrations(root: Path, cfg: dict, base: str | None, rep: Report) -> No ) -def check_tasks(root: Path, rep: Report) -> None: - tasks = root / "docs" / "tasks" - if not tasks.is_dir(): - rep.error("нет docs/tasks/ — каталог задач часть канона") - return - script = Path(__file__).resolve().parents[2] / "tasks" / "scripts" / "tasks.py" - if not script.exists(): - rep.skip(f"tasks.py не найден по пути {script} — согласованность задач не проверена") - return - # cwd=root обязателен: tasks.py отвергает --dir вне текущего каталога, и без - # этого его отказ окружения (код 3) схлопнулся бы в наш дрейф (код 1). - proc = subprocess.run( - [sys.executable, str(script), "check", "--dir", "docs/tasks"], - capture_output=True, - text=True, - cwd=str(root), - ) - if proc.returncode == 0: - return - if proc.returncode == 1: - rep.error("tasks.py check нашёл дрейф в docs/tasks/ — разбирать его командой tasks.py") - else: - # Чужой код выхода не выдаём за свой: 3 это окружение, а не дрейф. - rep.skip( - f"tasks.py check не отработал (код {proc.returncode}): " - f"{(proc.stderr or proc.stdout).strip().splitlines()[0] if (proc.stderr or proc.stdout).strip() else 'без сообщения'}" - ) - - # --- Отчёт ------------------------------------------------------------------ @@ -801,7 +776,6 @@ def cmd_check(args: argparse.Namespace) -> int: check_openspec(root, rep) check_capabilities(root, rep) check_migrations(root, cfg, args.base, rep) - check_tasks(root, rep) return report(rep) diff --git a/av-dev-tasks/skills/tasks/SKILL.md b/av-dev-tasks/skills/tasks/SKILL.md index 98c7617..06b2935 100644 --- a/av-dev-tasks/skills/tasks/SKILL.md +++ b/av-dev-tasks/skills/tasks/SKILL.md @@ -624,10 +624,17 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап действительно новый, а перевод чужой раскладки делает `av-dev-docs:canon`. У скрипта поиск вверх по дереву ещё жив — он для непереведённых проектов, и полагаться на него скилл не должен: молча найденный чужой каталог это дрейф. -- **Настройки живут в `docs/.pm.json`**, ключ `tasks`: **имена** файлов и - заголовков, и только если они отличаются от умолчания. Один конфиг на весь - канон, а не по одному на каталог. Неизвестный ключ — код 3 на любой команде, - так что лишнее слово в этом объекте останавливает работу с задачами целиком. +- **Настройки живут в `<каталог задач>/.tasks.json`** — свой файл у своего + плагина: **имена** файлов и заголовков, и только если они отличаются от + умолчания. Неизвестный ключ — код 3 на любой команде, так что лишнее слово в + этом объекте останавливает работу с задачами целиком. + + Дом именно свой, а не `docs/.pm.json`, потому что `docs/` принадлежит плагину + канона: проект, поставивший учёт работ без него, каталога `docs/` не имеет + вовсе. Прежний ключ `tasks` в `docs/.pm.json` читается, **только когда своего + файла нет** — для проектов, заведённых до раскола плагинов; скрипт при этом + говорит замечанием, куда его перенести. Есть оба — побеждает свой, и об этом + тоже говорится вслух: молча выбранный из двух конфиг это дрейф. - **Секции беклога** берутся из заголовков `##` индекса как есть; их количество и названия — дело проекта (умолчание `Ядро` / `Инфра`). **В конфиге их нет** — второй список разошёлся бы с заголовками молча. diff --git a/av-dev-tasks/skills/tasks/scripts/tasks.py b/av-dev-tasks/skills/tasks/scripts/tasks.py index 5997b7f..4798daf 100755 --- a/av-dev-tasks/skills/tasks/scripts/tasks.py +++ b/av-dev-tasks/skills/tasks/scripts/tasks.py @@ -108,8 +108,8 @@ import subprocess import sys from pathlib import Path -CONFIG_NAME = ".tasks.json" # прежний дом настроек, читается для совместимости -PM_CONFIG_REL = "../.pm.json" # текущий дом: docs/.pm.json, ключ "tasks" +CONFIG_NAME = ".tasks.json" # дом настроек: свой файл в каталоге задач +PM_CONFIG_REL = "../.pm.json" # прежний дом: docs/.pm.json, ключ "tasks" EXIT_OK = 0 EXIT_DRIFT = 1 @@ -443,27 +443,43 @@ class Layout: def load_config(root: Path) -> dict: """Настройки каталога задач. - Дом один — `docs/.pm.json`, ключ `tasks`: один конфиг на весь канон, а не по - одному на каталог. Прежний `/.tasks.json` читается, пока живы проекты, - которые ещё не переехали; когда есть оба, побеждает `.pm.json`, и об этом - говорится вслух, потому что молча выбранный из двух конфиг — это дрейф, - который потом никто не объяснит. + Дом — `<каталог задач>/.tasks.json`: **свой файл у своего плагина**. Ключ + `tasks` в `docs/.pm.json` читается, пока живы проекты, заведённые до раскола + плагинов, и только когда своего файла нет; когда есть оба, побеждает свой, и + об этом говорится вслух — молча выбранный из двух конфиг это дрейф, который + потом никто не объяснит. + + Порядок именно такой, а не наоборот, потому что `docs/` принадлежит другому + плагину. Проект, поставивший учёт задач без канона документов, каталога + `docs/` не имеет вовсе, и дом настроек, лежащий в чужом дереве, был бы домом, + которого у половины проектов нет. """ + path = root / CONFIG_NAME pm = (root / PM_CONFIG_REL).resolve() + if path.is_file(): + # Чужой конфиг здесь только повод для замечания, поэтому его поломка не + # наша: битый `docs/.pm.json` не должен ронять задачи, у которых свой + # файл на месте и читается. + try: + stale = pm.is_file() and isinstance(_read_json(pm).get("tasks"), dict) + except Env: + stale = False + if stale: + print(f"ЗАМЕЧАНИЕ настройки взяты из {path}; ключ «tasks» в {pm}" + f" остался от прежней раскладки и не читается — убери его", + file=sys.stderr) + return _validate_config(_read_json(path), path) if pm.is_file(): data = _read_json(pm) section = data.get("tasks", {}) if not isinstance(section, dict): raise Env(f"{pm}: ключ «tasks» — ожидался объект с настройками") - if (root / CONFIG_NAME).is_file(): - print(f"ЗАМЕЧАНИЕ настройки взяты из {pm}; {root / CONFIG_NAME}" - f" остался от прежней раскладки и не читается — удали его", - file=sys.stderr) + if section: + print(f"ЗАМЕЧАНИЕ настройки взяты из ключа «tasks» в {pm} — это" + f" прежний дом. Перенеси их в {path}: каталог docs/ ведёт" + f" другой плагин, и его может не быть", file=sys.stderr) return _validate_config(section, pm) - path = root / CONFIG_NAME - if not path.is_file(): - return {} - return _validate_config(_read_json(path), path) + return {} def _read_json(path: Path) -> dict: @@ -500,14 +516,14 @@ def _validate_config(data: dict, path: Path) -> dict: def config_home(root: Path) -> Path | None: """Откуда настройки читаются на самом деле — и куда, значит, слать чинить. - Порядок тот же, что в `load_config`: `docs/.pm.json` побеждает. Без этой - функции сообщения об ошибке звали править `.tasks.json`, который при живом - `.pm.json` вообще не читается. + Порядок тот же, что в `load_config`: свой `.tasks.json` побеждает. Без этой + функции сообщения об ошибке звали бы править файл, который не читается. """ + path = root / CONFIG_NAME + if path.is_file(): + return path pm = (root / PM_CONFIG_REL).resolve() - if pm.is_file(): - return pm - return root / CONFIG_NAME if (root / CONFIG_NAME).is_file() else None + return pm if pm.is_file() else None def config_problems(lay: Layout) -> list[str]: @@ -2734,20 +2750,12 @@ def init_files(lay: Layout, sections: list[str], roadmap_sections: list[str], cfg: dict) -> dict[Path, str]: out: dict[Path, str] = {} if cfg: - # Дом настроек один — `docs/.pm.json`, ключ `tasks`. Писать в - # `.tasks.json` при живом `.pm.json` значит писать туда, откуда никто - # не читает: load_config его в этом случае игнорирует. - pm = (lay.root / PM_CONFIG_REL).resolve() - if pm.is_file(): - data = _read_json(pm) - section = data.get("tasks") or {} - if not isinstance(section, dict): - raise Env(f"{pm}: ключ «tasks» — ожидался объект с настройками") - data["tasks"] = {**section, **cfg} - out[pm] = json.dumps(data, ensure_ascii=False, indent=2) + "\n" - else: - out[lay.root / CONFIG_NAME] = json.dumps(cfg, ensure_ascii=False, - indent=2) + "\n" + # Пишем всегда в свой `.tasks.json`, даже когда рядом живёт + # `docs/.pm.json`: дом настроек принадлежит этому плагину, а `docs/` — + # другому, и его в проекте может не быть. load_config читает свой файл + # первым, так что записанное сюда и прочитается отсюда. + out[lay.root / CONFIG_NAME] = json.dumps(cfg, ensure_ascii=False, + indent=2) + "\n" out[lay.index("backlog")] = ( "# Беклог\n\n" f"Что **можно взять**. Одна задача = один файл `{lay.cfg['items']}/.md`\n" @@ -2833,8 +2841,7 @@ def cmd_init(root: Path, a: argparse.Namespace) -> int: print(f" секции беклога: {', '.join(sections)};" f" секции роадмапа канонические: {', '.join(roadmap_sections)}") if cfg: - pm = (root / PM_CONFIG_REL).resolve() - print(f" имена частей записаны в {pm if pm.is_file() else root / CONFIG_NAME}") + print(f" имена частей записаны в {root / CONFIG_NAME}") return EXIT_OK