From 2db3625f17027ec17ac0317a75f668cbcb8f3444 Mon Sep 17 00:00:00 2001 From: hubooy Date: Sun, 9 Aug 2026 01:51:59 +0800 Subject: [PATCH] fix(playbook_loader): load generated playbooks from styles/custom/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit lib/playbook_generator.save_playbook() writes generated playbooks to styles/custom/, and the skills tell agents to save them there (skills/meta/capability-extension.md, the animation and explainer proposal directors). But this loader globs only the styles/ root, and it is the loader tools/video/video_compose.py imports — so every generated playbook was invisible at render time. The only way to make one work was to hand-copy it up one directory, which is why custom playbooks end up duplicated in both places. Search styles/custom/ as a fallback in load_playbook() and merge it into list_playbooks(), matching the semantics lib/playbook_generator.py already uses: a preset wins when both exist, and the listing is deduped. The not-found error now names both directories it searched instead of just the last path it tried. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01GYThSL15CujvBwD1wuUmr9 --- styles/playbook_loader.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/styles/playbook_loader.py b/styles/playbook_loader.py index 70155eb4..c1a5068a 100644 --- a/styles/playbook_loader.py +++ b/styles/playbook_loader.py @@ -17,6 +17,10 @@ import yaml import jsonschema STYLES_DIR = Path(__file__).resolve().parent +# Generated playbooks are written to styles/custom/ by +# lib/playbook_generator.save_playbook(); both lookups below search it as a +# fallback so those playbooks are visible to the render path. +CUSTOM_SUBDIR = "custom" SCHEMA_PATH = ( Path(__file__).resolve().parent.parent / "schemas" @@ -33,6 +37,9 @@ def _load_playbook_schema() -> dict: def load_playbook(name: str, styles_dir: Optional[Path] = None) -> dict[str, Any]: """Load and validate a style playbook by name. + Presets live directly in the styles dir; generated playbooks live in its + ``custom/`` subdirectory. A preset wins when both exist. + Args: name: Playbook name (without .yaml extension). styles_dir: Override directory for playbook files. @@ -43,7 +50,12 @@ def load_playbook(name: str, styles_dir: Optional[Path] = None) -> dict[str, Any styles_dir = styles_dir or STYLES_DIR path = styles_dir / f"{name}.yaml" if not path.exists(): - raise FileNotFoundError(f"Playbook not found: {path}") + path = styles_dir / CUSTOM_SUBDIR / f"{name}.yaml" + if not path.exists(): + raise FileNotFoundError( + f"Playbook not found: {name!r} " + f"(searched {styles_dir} and {styles_dir / CUSTOM_SUBDIR})" + ) with open(path, encoding="utf-8") as f: playbook = yaml.safe_load(f) @@ -59,13 +71,17 @@ def validate_playbook(playbook: dict) -> None: def list_playbooks(styles_dir: Optional[Path] = None) -> list[str]: - """List all available playbook names.""" + """List all available playbook names (presets + generated custom ones).""" styles_dir = styles_dir or STYLES_DIR - return [ + names = [ p.stem for p in styles_dir.glob("*.yaml") if p.stem != "__pycache__" ] + custom_dir = styles_dir / CUSTOM_SUBDIR + if custom_dir.exists(): + names.extend(p.stem for p in custom_dir.glob("*.yaml")) + return sorted(set(names)) # ---------------------------------------------------------------------------