diff --git a/scripts/playbook.py b/scripts/playbook.py index 87afb293..6bdbfdd5 100644 --- a/scripts/playbook.py +++ b/scripts/playbook.py @@ -721,9 +721,56 @@ def sync_agents_template(context: dict) -> int: date_value, playbook_scripts, ) + sync_claude_md(project_root) return 0 +_CLAUDE_BLOCK_START = "" +_CLAUDE_BLOCK_END = "" + + +def sync_claude_md(project_root: Path) -> None: + claude_md = project_root / "CLAUDE.md" + if not claude_md.exists(): + return + + block_lines = [ + _CLAUDE_BLOCK_START, + "", + "@AGENTS.md", + "@AGENT_RULES.md", + "", + _CLAUDE_BLOCK_END, + ] + + text = claude_md.read_text(encoding="utf-8") + + if _CLAUDE_BLOCK_START in text: + lines = text.splitlines() + updated: list[str] = [] + in_block = False + replaced = False + for line in lines: + if not replaced and line.strip() == _CLAUDE_BLOCK_START: + updated.extend(block_lines) + in_block = True + replaced = True + continue + if in_block: + if line.strip() == _CLAUDE_BLOCK_END: + in_block = False + continue + updated.append(line) + claude_md.write_text("\n".join(updated) + "\n", encoding="utf-8") + log("Updated CLAUDE.md (playbook block).") + elif "@AGENTS.md" in text: + log("Skip: CLAUDE.md already references AGENTS.md") + else: + appended = text.rstrip("\n") + "\n\n" + "\n".join(block_lines) + "\n" + claude_md.write_text(appended, encoding="utf-8") + log("Appended playbook block to CLAUDE.md") + + def should_sync_agents(config: dict) -> bool: for key in ("sync_rules", "sync_memory_bank", "sync_prompts", "sync_standards"): if key in config: diff --git a/tests/cli/test_playbook_cli.py b/tests/cli/test_playbook_cli.py index a44a0d9d..ac7d739f 100644 --- a/tests/cli/test_playbook_cli.py +++ b/tests/cli/test_playbook_cli.py @@ -487,5 +487,101 @@ skills = ["style-cleanup"] root, agents_home, f"{CUSTOM_DEPLOY_ROOT}/docs" ) + def test_sync_claude_md_skips_when_no_claude_md(self): + with tempfile.TemporaryDirectory() as tmp_dir: + config_body = f""" +[playbook] +project_root = "{tmp_dir}" +deploy_root = "{CUSTOM_DEPLOY_ROOT}" + +[sync_memory_bank] +project_name = "Demo" +""" + config_path = Path(tmp_dir) / "playbook.toml" + config_path.write_text(config_body, encoding="utf-8") + + result = run_cli("-config", str(config_path)) + + self.assertEqual(result.returncode, 0) + self.assertFalse((Path(tmp_dir) / "CLAUDE.md").exists()) + + def test_sync_claude_md_appends_block_to_existing_claude_md(self): + with tempfile.TemporaryDirectory() as tmp_dir: + claude_md = Path(tmp_dir) / "CLAUDE.md" + claude_md.write_text("# My project\n\nSome existing content.\n", encoding="utf-8") + + config_body = f""" +[playbook] +project_root = "{tmp_dir}" +deploy_root = "{CUSTOM_DEPLOY_ROOT}" + +[sync_memory_bank] +project_name = "Demo" +""" + config_path = Path(tmp_dir) / "playbook.toml" + config_path.write_text(config_body, encoding="utf-8") + + result = run_cli("-config", str(config_path)) + + self.assertEqual(result.returncode, 0) + text = claude_md.read_text(encoding="utf-8") + self.assertIn("@AGENTS.md", text) + self.assertIn("@AGENT_RULES.md", text) + self.assertIn("", text) + self.assertIn("Some existing content.", text) + + def test_sync_claude_md_updates_existing_block(self): + with tempfile.TemporaryDirectory() as tmp_dir: + claude_md = Path(tmp_dir) / "CLAUDE.md" + claude_md.write_text( + "# My project\n\n" + "\n" + "@AGENTS.md\n" + "\n", + encoding="utf-8", + ) + + config_body = f""" +[playbook] +project_root = "{tmp_dir}" +deploy_root = "{CUSTOM_DEPLOY_ROOT}" + +[sync_memory_bank] +project_name = "Demo" +""" + config_path = Path(tmp_dir) / "playbook.toml" + config_path.write_text(config_body, encoding="utf-8") + + result = run_cli("-config", str(config_path)) + + self.assertEqual(result.returncode, 0) + text = claude_md.read_text(encoding="utf-8") + self.assertIn("@AGENTS.md", text) + self.assertIn("@AGENT_RULES.md", text) + self.assertEqual(text.count(""), 1) + + def test_sync_claude_md_skips_when_already_references_agents(self): + with tempfile.TemporaryDirectory() as tmp_dir: + claude_md = Path(tmp_dir) / "CLAUDE.md" + original = "# My project\n\n@AGENTS.md\n" + claude_md.write_text(original, encoding="utf-8") + + config_body = f""" +[playbook] +project_root = "{tmp_dir}" +deploy_root = "{CUSTOM_DEPLOY_ROOT}" + +[sync_memory_bank] +project_name = "Demo" +""" + config_path = Path(tmp_dir) / "playbook.toml" + config_path.write_text(config_body, encoding="utf-8") + + result = run_cli("-config", str(config_path)) + + self.assertEqual(result.returncode, 0) + self.assertEqual(claude_md.read_text(encoding="utf-8"), original) + + if __name__ == "__main__": unittest.main()