docs: канон поднят с версии 7 до 12

- каталог задач переехал в tasks/ в корне, спринт упразднён — приоритет
  теперь порядок строк в BACKLOG.md, четыре задачи набора вернулись в беклог
- гейт: путь docs.py переведён на av-dev-docs вместо снесённого av-dev-pm,
  добавлены шаги tasks.py check и openspec.py check
- относительные ссылки внутри задач и ссылки из docs/ на задачи починены
This commit is contained in:
av
2026-08-09 19:09:53 +03:00
parent 9a624d4e13
commit c5d62d76ee
54 changed files with 109 additions and 79 deletions
+64 -26
View File
@@ -26,16 +26,20 @@ from pathlib import Path
OUT_DIR = Path("tmp/gate")
# docs.py скилла av-dev-pm:canon — проверка раскладки docs/ против канона.
# Скрипты трёх плагинов av-dev. Каждый проверяет своё и ни один не тянет
# остальных: без своего шага дрейф соответствующего каталога перестаёт ловиться
# молча.
# docs.py — раскладка docs/ против канона (av-dev-docs:canon)
# tasks.py — согласованность индексов каталога задач (av-dev-tasks:tasks)
# openspec.py — форма openspec/config.yaml (av-dev-code:openspec)
# Путь берётся из каталога маркетплейса (он без хеша версии, в отличие от
# cache/), переопределяется переменной DOCS_PY: переустановка плагина не должна
# cache/), переопределяется переменной: переустановка плагина не должна
# требовать правки гейта. Если скрипта нет — шаг краснеет, а не пропускается:
# молча пропущенная проверка даёт ложное ощущение проверенности.
DOCS_PY_DEFAULT = (
Path.home()
/ ".claude/plugins/marketplaces/av-dev-skills"
/ "av-dev-pm/skills/canon/scripts/docs.py"
)
MARKETPLACE = Path.home() / ".claude/plugins/marketplaces/av-dev-skills"
DOCS_PY_DEFAULT = MARKETPLACE / "av-dev-docs/skills/canon/scripts/docs.py"
TASKS_PY_DEFAULT = MARKETPLACE / "av-dev-tasks/skills/tasks/scripts/tasks.py"
OPENSPEC_PY_DEFAULT = MARKETPLACE / "av-dev-code/skills/openspec/scripts/openspec.py"
OK, FAIL, WARN, SKIP = "OK", "FAIL", "WARN", "SKIP"
@@ -92,6 +96,34 @@ def step(name: str, cmd: list[str], hint: str = "", env: dict[str, str] | None =
return ok
def plugin_step(
name: str, var: str, default: Path, plugin: str, args: list[str], hint: str
) -> None:
"""Шаг, который гоняет скрипт плагина av-dev.
Отсутствие скрипта краснеет, а не пропускается: молча пропущенная проверка
неотличима от пройденной. Коды выхода у всех трёх скриптов общие: 0 сошлось,
1 дрейф, 2 ошибка употребления, 3 окружение, 4 внутренний сбой. Различать 1
и остальные обязательно — дрейф чинится правкой проекта, прочее нет.
"""
script = Path(os.environ.get(var) or default)
if not script.is_file():
record(FAIL, name, f"{script.name} не найден: {script} — переустанови"
f" плагин {plugin} или задай путь переменной {var}")
return
log = OUT_DIR / f"{name}.log"
proc = subprocess.run(
["python3", str(script), *args], capture_output=True, text=True,
)
log.write_text(proc.stdout + proc.stderr, encoding="utf-8")
if proc.returncode == 0:
record(OK, name)
elif proc.returncode == 1:
record(FAIL, name, f"{hint}{log}")
else:
record(FAIL, name, f"{script.name} не отработал (код {proc.returncode}) → {log}")
def main() -> int:
OUT_DIR.mkdir(parents=True, exist_ok=True)
base = base_rev(sys.argv)
@@ -186,25 +218,31 @@ def main() -> int:
# нетронутые плейсхолдеры и две сверки с кодом — включая «миграция изменена,
# а database.md нет» (отдельного шага er-schema поэтому больше нет).
# База диффа обязательна: без неё сверка миграций со схемой не гоняется.
docs_py = Path(os.environ.get("DOCS_PY") or DOCS_PY_DEFAULT)
if not docs_py.is_file():
record(FAIL, "canon", f"docs.py не найден: {docs_py} — переустанови плагин"
" av-dev-pm или задай путь переменной DOCS_PY")
else:
log = OUT_DIR / "canon.log"
proc = subprocess.run(
["python3", str(docs_py), "check", "--dir", ".", "--base", base],
capture_output=True, text=True,
)
log.write_text(proc.stdout + proc.stderr, encoding="utf-8")
if proc.returncode == 0:
record(OK, "canon")
elif proc.returncode == 1:
record(FAIL, "canon", f"раскладка docs/ разошлась с каноном → {log}")
else:
# 2 — ошибка употребления, 3 — окружение, 4 — внутренний сбой.
# Это не дрейф документации, и чинится оно не правкой docs/.
record(FAIL, "canon", f"docs.py не отработал (код {proc.returncode}) → {log}")
plugin_step(
"canon", "DOCS_PY", DOCS_PY_DEFAULT, "av-dev-docs",
["check", "--dir", ".", "--base", base],
"раскладка docs/ разошлась с каноном",
)
# --- Каталог задач ---
# Согласованность индексов с файлами items/: бездомная запись, строка без
# файла, порядок секций роадмапа, сырьё не в конце секции. docs.py этого не
# смотрит — каталог принадлежит другому плагину.
plugin_step(
"tasks", "TASKS_PY", TASKS_PY_DEFAULT, "av-dev-tasks",
["check", "--dir", "tasks"],
"индексы задач разошлись с items/",
)
# --- Форма настройки OpenSpec ---
# Незаменённый пример в openspec/config.yaml читается как настроенный файл,
# а работает как пустой. Ловит это только свой шаг: docs.py про config.yaml
# молчит с канона 10.
plugin_step(
"openspec", "OPENSPEC_PY", OPENSPEC_PY_DEFAULT, "av-dev-code",
["check", "--dir", "."],
"openspec/config.yaml разошёлся с канонической формой",
)
# --- Секреты ---
# Гоняем всегда: секрет утекает из любого файла, не только из кода.