канон отпустил каталог задач: 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:
@@ -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. Объяви переходное состояние
|
||||||
|
|
||||||
|
|||||||
@@ -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).
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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` читается, **только когда своего
|
||||||
|
файла нет** — для проектов, заведённых до раскола плагинов; скрипт при этом
|
||||||
|
говорит замечанием, куда его перенести. Есть оба — побеждает свой, и об этом
|
||||||
|
тоже говорится вслух: молча выбранный из двух конфиг это дрейф.
|
||||||
- **Секции беклога** берутся из заголовков `##` индекса как есть; их количество
|
- **Секции беклога** берутся из заголовков `##` индекса как есть; их количество
|
||||||
и названия — дело проекта (умолчание `Ядро` / `Инфра`). **В конфиге их нет** —
|
и названия — дело проекта (умолчание `Ядро` / `Инфра`). **В конфиге их нет** —
|
||||||
второй список разошёлся бы с заголовками молча.
|
второй список разошёлся бы с заголовками молча.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user