av-dev-pipeline: бриф удалён, проходы читают документы канона напрямую

- удалены скилл project-brief и контракт брифа; вместо них references/
  project-facts.md — карта «что нужно проходу → где лежит» и таблица
  поразрядной деградации по документам
- девять charter'ов, review-pipeline, task-pipeline и task-batch переписаны
  на пути канона; OpenSpec стал объявленной предпосылкой без ветки деградации
- шаг синка документации переписан в построчный доклад, закрытие задачи —
  вызовом скилла av-dev-pm:tasks вместо строки-слота из CLAUDE.md
- по находкам ревью: docs.py звал tasks.py из чужого каталога и выдавал его
  отказ окружения за дрейф; сверка миграций не видела рабочее дерево;
  плейсхолдер краснел вместо замечания; сверка capability проходила по
  совпадению с именем пакета; tasks.py не читал docs/.pm.json; скилл docs
  пересказывал канон в пяти местах
This commit is contained in:
av
2026-08-03 14:28:55 +03:00
parent ad1779b81f
commit 9cef45252c
26 changed files with 687 additions and 1232 deletions
+33 -5
View File
@@ -83,7 +83,8 @@ import subprocess
import sys
from pathlib import Path
CONFIG_NAME = ".tasks.json"
CONFIG_NAME = ".tasks.json" # прежний дом настроек, читается для совместимости
PM_CONFIG_REL = "../.pm.json" # текущий дом: docs/.pm.json, ключ "tasks"
EXIT_OK = 0
EXIT_DRIFT = 1
@@ -278,15 +279,42 @@ class Layout:
def load_config(root: Path) -> dict:
"""Настройки каталога задач.
Дом один — `docs/.pm.json`, ключ `tasks`: один конфиг на весь канон, а не по
одному на каталог. Прежний `<tasks>/.tasks.json` читается, пока живы проекты,
которые ещё не переехали; когда есть оба, побеждает `.pm.json`, и об этом
говорится вслух, потому что молча выбранный из двух конфиг — это дрейф,
который потом никто не объяснит.
"""
pm = (root / PM_CONFIG_REL).resolve()
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)
return _validate_config(section, pm)
path = root / CONFIG_NAME
if not path.is_file():
return {}
return _validate_config(_read_json(path), path)
def _read_json(path: Path) -> dict:
try:
data = json.loads(path.read_text(encoding="utf-8"))
except json.JSONDecodeError as e:
raise Env(f"{path}: не разбирается как JSON — {e}")
if not isinstance(data, dict):
raise Env(f"{path}: ожидался объект с настройками")
return data
def _validate_config(data: dict, path: Path) -> dict:
unknown = set(data) - set(DEFAULTS)
if unknown:
raise Env(f"{path}: неизвестные ключи: {', '.join(sorted(unknown))}"
@@ -351,10 +379,10 @@ def resolve_layout(explicit: str | None) -> Layout:
return Layout(rel if str(rel) != "." else candidate, load_config(candidate))
if (base / ".git").exists():
break # выше корня репозитория не ищем
raise Env("каталог задач не найден: ни --dir, ни .tasks.json вверх от"
f" {here}, ни умолчания (docs/tasks, tasks, doc/tasks)."
" Путь каталога называет CLAUDE.md проекта; новый проект —"
" tasks.py init --dir <путь>")
raise Env("каталог задач не найден: ни --dir, ни docs/tasks вверх от"
f" {here}. По канону путь всегда docs/tasks; чужую раскладку"
" переводит скилл av-dev-pm:canon, новый проект —"
" tasks.py init --dir docs/tasks")
# --- Чтение индексов ---