канон отпустил каталог задач: docs.py не зовёт tasks.py, конфиг разъехался

Пока владелец был один, 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) <noreply@anthropic.com>
This commit is contained in:
av
2026-08-09 14:12:08 +03:00
co-authored by Claude Opus 5
parent 00ddfb0dde
commit 1f31ac6afd
7 changed files with 131 additions and 110 deletions
+8 -7
View File
@@ -170,13 +170,14 @@ capability), `openspec/config.yaml`.
9. `docs.py check` — до **отсутствия дрейфа раскладки**. Замечания 9. `docs.py check` — до **отсутствия дрейфа раскладки**. Замечания
(незаполненные плейсхолдеры, слабое упоминание capability) остаются: (незаполненные плейсхолдеры, слабое упоминание capability) остаются:
незаполненный канон это объявленное переходное состояние из шага 5, а не незаполненный канон это объявленное переходное состояние из шага 5, а не
отказ. **Пункт «задачи без цели» из вложенной проверки `tasks.py` тоже отказ. Пересчитай эти пункты в докладе переходного состояния — не выдавай
остаётся** и зелёным на этом шаге не станет: цели не сочиняются адаптацией их за поломку и не молчи о них.
(запрет записан у того, кто ведёт задачи, — сценарий адаптации скилла
`av-dev-tasks:tasks`), их **Задачи `docs.py` не проверяет** — их ведёт другой плагин, и согласованность
проставляет человек порциями переоценки на первой сессии. Пересчитай эти каталога показывает только `tasks.py check`. Позвал на шаге 5 скилл задач —
пункты в докладе переходного состояния — не выдавай их за поломку и не его отчёт идёт в доклад отдельной строкой, и пункт «задачи без цели» в нём
молчи о них. зелёным не станет: цели не сочиняются адаптацией (запрет записан у того, кто
ведёт задачи), их проставляет человек порциями переоценки на первой сессии.
### 5. Объяви переходное состояние ### 5. Объяви переходное состояние
+23 -24
View File
@@ -75,8 +75,8 @@ docs/
adr.md | adr/ почему решено так; статусы, правило замены adr.md | adr/ почему решено так; статусы, правило замены
review.md | review/ настройка конвейера под проект + журнал дефектов review.md | review/ настройка конвейера под проект + журнал дефектов
<своя тема>.md | <своя тема>/ всё, что проект счёл нужным проверять <своя тема>.md | <своя тема>/ всё, что проект счёл нужным проверять
tasks/ скилл tasks: items/, ROADMAP.md, BACKLOG.md, tasks/ каталог задач — плагин av-dev-tasks, не канон:
SPRINT.md, REJECTED.md место зарезервировано, наличия канон не требует
openspec/ openspec/
config.yaml только нужды генерации артефактов + ссылки config.yaml только нужды генерации артефактов + ссылки
specs/<capability>/spec.md что система делает — нормативно specs/<capability>/spec.md что система делает — нормативно
@@ -115,7 +115,7 @@ openspec/
| `CLAUDE.md`, `AGENTS.md` | источник | `autotests` (семантика гейта); инварианты — сквозные | | `CLAUDE.md`, `AGENTS.md` | источник | `autotests` (семантика гейта); инварианты — сквозные |
| `openspec/specs/` | источник | `requirements` | | `openspec/specs/` | источник | `requirements` |
| `openspec/config.yaml` | процессный | — (настройка порождения артефактов, слой **до** тем) | | `openspec/config.yaml` | процессный | — (настройка порождения артефактов, слой **до** тем) |
| `tasks/` | процессный | — | | `tasks/` | процессный | — (чужое владение: плагин `av-dev-tasks`) |
| `review.*` | процессный | — (настройка самого конвейера, слой **над** темами) | | `review.*` | процессный | — (настройка самого конвейера, слой **над** темами) |
| `adr.*` | процессный | — | | `adr.*` | процессный | — |
| `research.*` | процессный | — | | `research.*` | процессный | — |
@@ -353,9 +353,16 @@ kebab-case.** Причина не эстетическая: имя файла с
### `tasks/` ### `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` отвечает на «что приложение уже умеет и чего ещё не умеет».** Это **`ROADMAP.md` отвечает на «что приложение уже умеет и чего ещё не умеет».** Это
не очередь работ: цель — **возможность приложения**, задача — шаг к ней. не очередь работ: цель — **возможность приложения**, задача — шаг к ней.
@@ -563,30 +570,22 @@ OpenSpec переименует артефакт или сменит схему
```json ```json
{ {
"canon": 7, "canon": 8,
"migrations": "internal/store/migrations", "migrations": "internal/store/migrations"
"tasks": {
"backlog": "INDEX.md"
}
} }
``` ```
`canon` — версия канона, под которую проект приведён, целым числом: обратной `canon` — версия канона, под которую проект приведён, целым числом: обратной
совместимости у канона нет, есть «приведён» и «не приведён». `migrations` — путь совместимости у канона нет, есть «приведён» и «не приведён». `migrations` — путь
каталога миграций, если БД есть; по нему `docs.py` делает сверку с каталога миграций, если БД есть; по нему `docs.py` делает сверку с
`database.md`. `tasks` — настройки каталога задач, переехавшие сюда из прежнего `database.md`.
`<tasks>/.tasks.json`: **один конфиг на весь канон, а не по одному на каталог**.
Внутри `tasks`**только имена файлов и заголовков** (`items`, `backlog`, **Ключа `tasks` здесь больше нет.** Настройки каталога задач вернулись в свой
`roadmap`, `sprint`, `rejected`, `sprint_section`, `oracle_word` и заголовки файл `<каталог задач>/.tasks.json`, потому что ведёт их другой плагин: конфиг,
разделов тела: `criteria_heading`, `surface_heading`, `questions_heading`, лежащий в `docs/`, был бы домом, которого нет у проекта, поставившего учёт работ
`completion_heading`, `repro_heading`, `question_heading`, `answer_heading`, без канона документов. Состав ключей описывает тот плагин, а не канон. Прежний
`scope_heading`), и ключ пишется, лишь когда имя отличается от умолчания. ключ читается, пока живы непереехавшие проекты, и `tasks.py` говорит о нём
**Словаря типов здесь нет** — он закрыт каноном, а не настраивается проектом: замечанием на каждом прогоне — версия 8 журнала просит его убрать.
настраиваемый словарь типов разъехался бы на синонимах ровно так же, как
открытый. **Категорий беклога здесь тоже нет:** их дом — заголовки `##` самого
индекса, и второй список сразу разошёлся бы с первым. Неизвестный ключ `tasks.py`
отвергает кодом 3, поэтому лишнее слово в этом объекте останавливает работу с
задачами целиком.
Ключей будет больше по мере роста проверок; неизвестный ключ `docs.py` Ключей будет больше по мере роста проверок; неизвестный ключ `docs.py`
игнорирует, отсутствующий — считает «проверка неприменима» и говорит об этом игнорирует, отсутствующий — считает «проверка неприменима» и говорит об этом
@@ -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 ## Версия 7 — 2026-08-07
`openspec/` был предпосылкой, о которой канон говорил, но за которой не следил. `openspec/` был предпосылкой, о которой канон говорил, но за которой не следил.
@@ -501,11 +501,10 @@ rules:
```json ```json
{ {
"canon": 7 "canon": 8
} }
``` ```
Плюс `"migrations": "<путь>"`, если есть БД. Ключ `"tasks"` заводится **только** Плюс `"migrations": "<путь>"`, если есть БД. Ключа `"tasks"` здесь **нет**:
когда имя файла или заголовка отличается от умолчания (`{"backlog": настройки каталога задач переехали в свой файл `<каталог задач>/.tasks.json`,
"INDEX.md"}`); секций беклога в нём нет — их дом заголовки `##` индекса. Состав потому что ведёт их другой плагин. Состав ключей — [canon.md](canon.md).
ключей — [canon.md](canon.md).
+7 -33
View File
@@ -25,7 +25,7 @@ from dataclasses import dataclass, field
from pathlib import Path from pathlib import Path
from typing import NoReturn from typing import NoReturn
CANON_VERSION = 7 CANON_VERSION = 8
OK, DRIFT, USAGE, ENV, INTERNAL = 0, 1, 2, 3, 4 OK, DRIFT, USAGE, ENV, INTERNAL = 0, 1, 2, 3, 4
@@ -110,8 +110,12 @@ OPENSPEC_SCHEMA = "spec-driven"
# проверка и заведена. # проверка и заведена.
OPENSPEC_ARTIFACTS = ("proposal", "specs", "design", "tasks") OPENSPEC_ARTIFACTS = ("proposal", "specs", "design", "tasks")
# Служебное в docs/ и каталог, который ведёт tasks.py. Оба процессные, но # Служебное в docs/ и каталог задач. Формы у них скрипт не проверяет, и по разным
# проверок формы у них нет: .pm.json не markdown, tasks/ ведёт другой скрипт. # причинам: `.pm.json` не markdown, а `tasks/` **принадлежит другому плагину** —
# `av-dev-tasks`, со своим скриптом, своим конфигом и своей версией формата.
# Канон резервирует за ним место в `docs/`, но наличия не требует и внутрь не
# смотрит: проект, поставивший только этот плагин, задач не ведёт вовсе, и
# отказом это быть не может.
NOT_DOCS = {".pm.json", "tasks"} 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_openspec(root, rep)
check_capabilities(root, rep) check_capabilities(root, rep)
check_migrations(root, cfg, args.base, rep) check_migrations(root, cfg, args.base, rep)
check_tasks(root, rep)
return report(rep) return report(rep)
+11 -4
View File
@@ -624,10 +624,17 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
действительно новый, а перевод чужой раскладки делает `av-dev-docs:canon`. действительно новый, а перевод чужой раскладки делает `av-dev-docs:canon`.
У скрипта поиск вверх по дереву ещё жив — он для непереведённых проектов, и У скрипта поиск вверх по дереву ещё жив — он для непереведённых проектов, и
полагаться на него скилл не должен: молча найденный чужой каталог это дрейф. полагаться на него скилл не должен: молча найденный чужой каталог это дрейф.
- **Настройки живут в `docs/.pm.json`**, ключ `tasks`: **имена** файлов и - **Настройки живут в `<каталог задач>/.tasks.json`** — свой файл у своего
заголовков, и только если они отличаются от умолчания. Один конфиг на весь плагина: **имена** файлов и заголовков, и только если они отличаются от
канон, а не по одному на каталог. Неизвестный ключ — код 3 на любой команде, умолчания. Неизвестный ключ — код 3 на любой команде, так что лишнее слово в
так что лишнее слово в этом объекте останавливает работу с задачами целиком. этом объекте останавливает работу с задачами целиком.
Дом именно свой, а не `docs/.pm.json`, потому что `docs/` принадлежит плагину
канона: проект, поставивший учёт работ без него, каталога `docs/` не имеет
вовсе. Прежний ключ `tasks` в `docs/.pm.json` читается, **только когда своего
файла нет** — для проектов, заведённых до раскола плагинов; скрипт при этом
говорит замечанием, куда его перенести. Есть оба — побеждает свой, и об этом
тоже говорится вслух: молча выбранный из двух конфиг это дрейф.
- **Секции беклога** берутся из заголовков `##` индекса как есть; их количество - **Секции беклога** берутся из заголовков `##` индекса как есть; их количество
и названия — дело проекта (умолчание `Ядро` / `Инфра`). **В конфиге их нет** — и названия — дело проекта (умолчание `Ядро` / `Инфра`). **В конфиге их нет** —
второй список разошёлся бы с заголовками молча. второй список разошёлся бы с заголовками молча.
+44 -37
View File
@@ -108,8 +108,8 @@ import subprocess
import sys import sys
from pathlib import Path from pathlib import Path
CONFIG_NAME = ".tasks.json" # прежний дом настроек, читается для совместимости CONFIG_NAME = ".tasks.json" # дом настроек: свой файл в каталоге задач
PM_CONFIG_REL = "../.pm.json" # текущий дом: docs/.pm.json, ключ "tasks" PM_CONFIG_REL = "../.pm.json" # прежний дом: docs/.pm.json, ключ "tasks"
EXIT_OK = 0 EXIT_OK = 0
EXIT_DRIFT = 1 EXIT_DRIFT = 1
@@ -443,27 +443,43 @@ class Layout:
def load_config(root: Path) -> dict: def load_config(root: Path) -> dict:
"""Настройки каталога задач. """Настройки каталога задач.
Дом один `docs/.pm.json`, ключ `tasks`: один конфиг на весь канон, а не по Дом `<каталог задач>/.tasks.json`: **свой файл у своего плагина**. Ключ
одному на каталог. Прежний `<tasks>/.tasks.json` читается, пока живы проекты, `tasks` в `docs/.pm.json` читается, пока живы проекты, заведённые до раскола
которые ещё не переехали; когда есть оба, побеждает `.pm.json`, и об этом плагинов, и только когда своего файла нет; когда есть оба, побеждает свой, и
говорится вслух, потому что молча выбранный из двух конфиг это дрейф, об этом говорится вслух молча выбранный из двух конфиг это дрейф, который
который потом никто не объяснит. потом никто не объяснит.
Порядок именно такой, а не наоборот, потому что `docs/` принадлежит другому
плагину. Проект, поставивший учёт задач без канона документов, каталога
`docs/` не имеет вовсе, и дом настроек, лежащий в чужом дереве, был бы домом,
которого у половины проектов нет.
""" """
path = root / CONFIG_NAME
pm = (root / PM_CONFIG_REL).resolve() 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(): if pm.is_file():
data = _read_json(pm) data = _read_json(pm)
section = data.get("tasks", {}) section = data.get("tasks", {})
if not isinstance(section, dict): if not isinstance(section, dict):
raise Env(f"{pm}: ключ «tasks» — ожидался объект с настройками") raise Env(f"{pm}: ключ «tasks» — ожидался объект с настройками")
if (root / CONFIG_NAME).is_file(): if section:
print(f"ЗАМЕЧАНИЕ настройки взяты из {pm}; {root / CONFIG_NAME}" print(f"ЗАМЕЧАНИЕ настройки взяты из ключа «tasks» в {pm} — это"
f" остался от прежней раскладки и не читается — удали его", f" прежний дом. Перенеси их в {path}: каталог docs/ ведёт"
file=sys.stderr) f" другой плагин, и его может не быть", file=sys.stderr)
return _validate_config(section, pm) return _validate_config(section, pm)
path = root / CONFIG_NAME return {}
if not path.is_file():
return {}
return _validate_config(_read_json(path), path)
def _read_json(path: Path) -> dict: 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: def config_home(root: Path) -> Path | None:
"""Откуда настройки читаются на самом деле — и куда, значит, слать чинить. """Откуда настройки читаются на самом деле — и куда, значит, слать чинить.
Порядок тот же, что в `load_config`: `docs/.pm.json` побеждает. Без этой Порядок тот же, что в `load_config`: свой `.tasks.json` побеждает. Без этой
функции сообщения об ошибке звали править `.tasks.json`, который при живом функции сообщения об ошибке звали бы править файл, который не читается.
`.pm.json` вообще не читается.
""" """
path = root / CONFIG_NAME
if path.is_file():
return path
pm = (root / PM_CONFIG_REL).resolve() pm = (root / PM_CONFIG_REL).resolve()
if pm.is_file(): return pm if pm.is_file() else None
return pm
return root / CONFIG_NAME if (root / CONFIG_NAME).is_file() else None
def config_problems(lay: Layout) -> list[str]: 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]: cfg: dict) -> dict[Path, str]:
out: dict[Path, str] = {} out: dict[Path, str] = {}
if cfg: if cfg:
# Дом настроек один — `docs/.pm.json`, ключ `tasks`. Писать в # Пишем всегда в свой `.tasks.json`, даже когда рядом живёт
# `.tasks.json` при живом `.pm.json` значит писать туда, откуда никто # `docs/.pm.json`: дом настроек принадлежит этому плагину, а `docs/` —
# не читает: load_config его в этом случае игнорирует. # другому, и его в проекте может не быть. load_config читает свой файл
pm = (lay.root / PM_CONFIG_REL).resolve() # первым, так что записанное сюда и прочитается отсюда.
if pm.is_file(): out[lay.root / CONFIG_NAME] = json.dumps(cfg, ensure_ascii=False,
data = _read_json(pm) indent=2) + "\n"
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"
out[lay.index("backlog")] = ( out[lay.index("backlog")] = (
"# Беклог\n\n" "# Беклог\n\n"
f"Что **можно взять**. Одна задача = один файл `{lay.cfg['items']}/<slug>.md`\n" f"Что **можно взять**. Одна задача = один файл `{lay.cfg['items']}/<slug>.md`\n"
@@ -2833,8 +2841,7 @@ def cmd_init(root: Path, a: argparse.Namespace) -> int:
print(f" секции беклога: {', '.join(sections)};" print(f" секции беклога: {', '.join(sections)};"
f" секции роадмапа канонические: {', '.join(roadmap_sections)}") f" секции роадмапа канонические: {', '.join(roadmap_sections)}")
if cfg: if cfg:
pm = (root / PM_CONFIG_REL).resolve() print(f" имена частей записаны в {root / CONFIG_NAME}")
print(f" имена частей записаны в {pm if pm.is_file() else root / CONFIG_NAME}")
return EXIT_OK return EXIT_OK