From 6518f0f554adae16497ce83e67830ee3f8b4a5c1 Mon Sep 17 00:00:00 2001 From: csh Date: Sat, 16 May 2026 13:29:33 +0800 Subject: [PATCH] :sparkles: feat(playbook): auto-create CLAUDE.md with path discovery sync_claude_md now: - Checks project root CLAUDE.md first, then .claude/CLAUDE.md - Auto-creates if neither exists (default: project root) - Supports playbook.claude_md config to override location - Update test to verify auto-creation behavior Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/playbook.py | 28 +++++++++++++++++++++++----- tests/cli/test_playbook_cli.py | 8 ++++++-- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/scripts/playbook.py b/scripts/playbook.py index 089e7144..840419c5 100644 --- a/scripts/playbook.py +++ b/scripts/playbook.py @@ -721,18 +721,30 @@ def sync_agents_template(context: dict) -> int: date_value, playbook_scripts, ) - sync_claude_md(project_root) + sync_claude_md(project_root, context.get("config", {})) return 0 _CLAUDE_BLOCK_START = "" _CLAUDE_BLOCK_END = "" +_CLAUDE_MD_CANDIDATES = ["CLAUDE.md", ".claude/CLAUDE.md"] -def sync_claude_md(project_root: Path) -> None: - claude_md = project_root / "CLAUDE.md" - if not claude_md.exists(): - return + +def sync_claude_md(project_root: Path, config: dict) -> None: + claude_md_config = config.get("playbook", {}).get("claude_md") + + claude_md: Path | None = None + if claude_md_config: + claude_md = project_root / claude_md_config + else: + for candidate in _CLAUDE_MD_CANDIDATES: + path = project_root / candidate + if path.exists(): + claude_md = path + break + if claude_md is None: + claude_md = project_root / "CLAUDE.md" block_lines = [ _CLAUDE_BLOCK_START, @@ -743,6 +755,12 @@ def sync_claude_md(project_root: Path) -> None: _CLAUDE_BLOCK_END, ] + if not claude_md.exists(): + ensure_dir(claude_md.parent) + claude_md.write_text("\n".join(block_lines) + "\n", encoding="utf-8") + log(f"Created {claude_md.relative_to(project_root)} with playbook block.") + return + text = claude_md.read_text(encoding="utf-8") if _CLAUDE_BLOCK_START in text: diff --git a/tests/cli/test_playbook_cli.py b/tests/cli/test_playbook_cli.py index 244d9f75..d8a7dc4e 100644 --- a/tests/cli/test_playbook_cli.py +++ b/tests/cli/test_playbook_cli.py @@ -487,7 +487,7 @@ skills = ["style-cleanup"] root, agents_home, f"{CUSTOM_DEPLOY_ROOT}/docs" ) - def test_sync_claude_md_skips_when_no_claude_md(self): + def test_sync_claude_md_creates_when_no_claude_md(self): with tempfile.TemporaryDirectory() as tmp_dir: config_body = f""" [playbook] @@ -503,7 +503,11 @@ project_name = "Demo" result = run_cli("-config", str(config_path)) self.assertEqual(result.returncode, 0) - self.assertFalse((Path(tmp_dir) / "CLAUDE.md").exists()) + claude_md = Path(tmp_dir) / "CLAUDE.md" + self.assertTrue(claude_md.exists()) + text = claude_md.read_text(encoding="utf-8") + self.assertIn("@AGENTS.md", text) + self.assertIn("", text) def test_sync_claude_md_appends_block_to_existing_claude_md(self): with tempfile.TemporaryDirectory() as tmp_dir: