import subprocess import sys import tempfile import unittest from pathlib import Path ROOT = Path(__file__).resolve().parents[2] SCRIPT = ROOT / "scripts" / "playbook.py" CUSTOM_DEPLOY_ROOT = "custom/playbook" def run_script(script, *args): return subprocess.run( [sys.executable, str(script), *args], capture_output=True, text=True, ) def run_cli(*args): return run_script(SCRIPT, *args) def write_config(root: Path, name: str, body: str) -> Path: config_path = root / name config_path.write_text(body, encoding="utf-8") return config_path class InstallSkillsTests(unittest.TestCase): def assert_style_cleanup_tsl_docs_prefix( self, root: Path, agents_home: Path, docs_prefix: str ) -> None: agents_index = (root / ".agents" / "tsl" / "index.md").read_text( encoding="utf-8" ) self.assertIn(f"`{docs_prefix}/tsl/index.md`", agents_index) self.assertNotIn("`docs/tsl/index.md`", agents_index) skill_file = (agents_home / "skills" / "style-cleanup" / "SKILL.md").read_text( encoding="utf-8" ) self.assertIn(f"`{docs_prefix}/tsl/code_style.md`", skill_file) self.assertNotIn("`docs/tsl/code_style.md`", skill_file) def test_install_skills(self): with tempfile.TemporaryDirectory() as tmp_dir: target = Path(tmp_dir) / "agents" config_body = f""" [playbook] project_root = "{tmp_dir}" playbook_root = "{CUSTOM_DEPLOY_ROOT}" install_mode = "snapshot" [install_skills] agents_home = "{target}" mode = "list" skills = ["brainstorming"] """ config_path = Path(tmp_dir) / "playbook.toml" config_path.write_text(config_body, encoding="utf-8") result = run_cli("-config", str(config_path)) skill_file = target / "skills/brainstorming/SKILL.md" self.assertEqual(result.returncode, 0) self.assertTrue(skill_file.is_file()) def test_install_skills_installs_generated_thirdparty_skill(self): with tempfile.TemporaryDirectory() as tmp_dir: target = Path(tmp_dir) / "agents" config_body = f""" [playbook] project_root = "{tmp_dir}" playbook_root = "{CUSTOM_DEPLOY_ROOT}" install_mode = "snapshot" [install_skills] agents_home = "{target}" mode = "list" skills = ["karpathy-guidelines"] """ config_path = Path(tmp_dir) / "playbook.toml" config_path.write_text(config_body, encoding="utf-8") result = run_cli("-config", str(config_path)) skill_file = target / "skills" / "karpathy-guidelines" / "SKILL.md" self.assertEqual(result.returncode, 0, msg=result.stdout + result.stderr) self.assertTrue(skill_file.is_file()) def test_install_skills_rejects_removed_tsl_guide(self): with tempfile.TemporaryDirectory() as tmp_dir: target = Path(tmp_dir) / "agents" config_body = f""" [playbook] project_root = "{tmp_dir}" playbook_root = "{CUSTOM_DEPLOY_ROOT}" install_mode = "snapshot" [install_skills] agents_home = "{target}" mode = "list" skills = ["tsl-guide"] """ config_path = Path(tmp_dir) / "playbook.toml" config_path.write_text(config_body, encoding="utf-8") result = run_cli("-config", str(config_path)) self.assertNotEqual(result.returncode, 0) self.assertIn("skill not found: tsl-guide", result.stdout + result.stderr) def test_install_skills_rejects_codex_home(self): with tempfile.TemporaryDirectory() as tmp_dir: target = Path(tmp_dir) / "codex" config_body = f""" [playbook] project_root = "{tmp_dir}" playbook_root = "{CUSTOM_DEPLOY_ROOT}" install_mode = "snapshot" [install_skills] codex_home = "{target}" mode = "list" skills = ["brainstorming"] """ config_path = Path(tmp_dir) / "playbook.toml" config_path.write_text(config_body, encoding="utf-8") result = run_cli("-config", str(config_path)) self.assertNotEqual(result.returncode, 0) self.assertIn("codex_home", result.stdout + result.stderr) def test_external_clone_flow_rewrites_links_with_configured_playbook_root(self): with tempfile.TemporaryDirectory() as tmp_dir: root = Path(tmp_dir) agents_home = root / "agents-home" config_body = f""" [playbook] project_root = "{tmp_dir}" playbook_root = "{CUSTOM_DEPLOY_ROOT}" install_mode = "snapshot" [sync_standards] langs = ["tsl"] no_backup = true [install_skills] agents_home = "{agents_home}" mode = "list" skills = ["style-cleanup"] """ config_path = write_config(root, "playbook.toml", config_body) result = run_cli("-config", str(config_path)) self.assertEqual(result.returncode, 0, msg=result.stdout + result.stderr) self.assertTrue((root / CUSTOM_DEPLOY_ROOT / "SOURCE.md").is_file()) self.assert_style_cleanup_tsl_docs_prefix( root, agents_home, f"{CUSTOM_DEPLOY_ROOT}/docs" ) def test_deployed_snapshot_rewrites_links_from_snapshot_location(self): with tempfile.TemporaryDirectory() as tmp_dir: root = Path(tmp_dir) install_config = write_config( root, "install.toml", f""" [playbook] project_root = "{tmp_dir}" playbook_root = "{CUSTOM_DEPLOY_ROOT}" install_mode = "snapshot" [sync_standards] langs = ["tsl"] """, ) install_result = run_cli("-config", str(install_config)) self.assertEqual( install_result.returncode, 0, msg=install_result.stdout + install_result.stderr, ) snapshot_script = root / CUSTOM_DEPLOY_ROOT / "scripts" / "playbook.py" agents_home = root / "local-agents" sync_config = write_config( root, "sync.toml", f""" [playbook] project_root = "{tmp_dir}" playbook_root = "{CUSTOM_DEPLOY_ROOT}" install_mode = "snapshot" [sync_standards] langs = ["tsl"] no_backup = true [install_skills] agents_home = "{agents_home}" mode = "list" skills = ["style-cleanup"] """, ) sync_result = run_script(snapshot_script, "-config", str(sync_config)) self.assertEqual( sync_result.returncode, 0, msg=sync_result.stdout + sync_result.stderr ) self.assert_style_cleanup_tsl_docs_prefix( root, agents_home, f"{CUSTOM_DEPLOY_ROOT}/docs" ) def test_install_skills_creates_symlink_when_skill_link_configured(self): with tempfile.TemporaryDirectory() as tmp_dir: root = Path(tmp_dir) agents_home = root / "agents" link_home = root / "claude" config_body = f""" [playbook] project_root = "{tmp_dir}" playbook_root = "{CUSTOM_DEPLOY_ROOT}" install_mode = "snapshot" [install_skills] agents_home = "{agents_home}" skill_link = "{link_home}" mode = "list" skills = ["commit-message"] """ config_path = root / "playbook.toml" config_path.write_text(config_body, encoding="utf-8") result = run_cli("-config", str(config_path)) self.assertEqual(result.returncode, 0, msg=result.stdout + result.stderr) skills_dst = agents_home / "skills" link_path = link_home / "skills" self.assertTrue(skills_dst.is_dir()) self.assertTrue(link_path.is_dir(), "link_path should be accessible as dir") self.assertEqual(link_path.resolve(), skills_dst.resolve()) self.assertTrue((link_path / "commit-message" / "SKILL.md").is_file()) def test_install_skills_symlink_is_idempotent(self): with tempfile.TemporaryDirectory() as tmp_dir: root = Path(tmp_dir) agents_home = root / "agents" link_home = root / "claude" config_body = f""" [playbook] project_root = "{tmp_dir}" playbook_root = "{CUSTOM_DEPLOY_ROOT}" install_mode = "snapshot" [install_skills] agents_home = "{agents_home}" skill_link = "{link_home}" mode = "list" skills = ["commit-message"] no_backup = true """ config_path = root / "playbook.toml" config_path.write_text(config_body, encoding="utf-8") run_cli("-config", str(config_path)) result = run_cli("-config", str(config_path)) self.assertEqual(result.returncode, 0, msg=result.stdout + result.stderr) self.assertTrue((link_home / "skills").is_dir()) def test_install_skills_no_symlink_when_skill_link_absent(self): with tempfile.TemporaryDirectory() as tmp_dir: root = Path(tmp_dir) agents_home = root / "agents" config_body = f""" [playbook] project_root = "{tmp_dir}" playbook_root = "{CUSTOM_DEPLOY_ROOT}" install_mode = "snapshot" [install_skills] agents_home = "{agents_home}" mode = "list" skills = ["commit-message"] """ config_path = root / "playbook.toml" config_path.write_text(config_body, encoding="utf-8") result = run_cli("-config", str(config_path)) self.assertEqual(result.returncode, 0, msg=result.stdout + result.stderr) self.assertFalse( any( p.is_symlink() for p in (agents_home / "skills").iterdir() if p.is_symlink() ) if (agents_home / "skills").exists() else False ) if __name__ == "__main__": unittest.main()