From 08ca87b74cabf92cfd92bc657d0060e2712ba50c Mon Sep 17 00:00:00 2001 From: csh Date: Thu, 23 Apr 2026 09:31:45 +0800 Subject: [PATCH] :package: deps(skills): add karpathy thirdparty sync --- .gitea/ci/thirdparty_skills.json | 9 +++ SKILLS.md | 8 +++ tests/cli/test_playbook_cli.py | 70 ++++++++++++++++++++++ tests/test_thirdparty_skills_pipeline.py | 76 +++++++++++++++++++++++- 4 files changed, 161 insertions(+), 2 deletions(-) diff --git a/.gitea/ci/thirdparty_skills.json b/.gitea/ci/thirdparty_skills.json index 0817b8c..9fa55f1 100644 --- a/.gitea/ci/thirdparty_skills.json +++ b/.gitea/ci/thirdparty_skills.json @@ -22,6 +22,15 @@ "template_root": "src/ui-ux-pro-max/templates", "data_dir": "src/ui-ux-pro-max/data", "scripts_dir": "src/ui-ux-pro-max/scripts" + }, + { + "id": "andrej-karpathy-skills", + "upstream_repo": "https://github.com/forrestchang/andrej-karpathy-skills.git", + "upstream_ref": "main", + "snapshot_dir": "andrej-karpathy-skills", + "sync_mode": "copy_skill_dirs", + "source_list": "codex/skills/.sources/andrej-karpathy-skills.list", + "skills_subdir": "skills" } ] } diff --git a/SKILLS.md b/SKILLS.md index 888990e..c3edf8c 100644 --- a/SKILLS.md +++ b/SKILLS.md @@ -158,6 +158,14 @@ python /scripts/playbook.py -config playbook.toml - `superpowers.list` - `ui-ux-pro-max.list` +- `andrej-karpathy-skills.list` + +部署链路: + +- `thirdparty/skill` 分支保存上游快照 `andrej-karpathy-skills/` +- 自动同步后,`skills/karpathy-guidelines/` 会落到 `codex/skills/karpathy-guidelines/` +- 运行 `[install_skills]` 时,再复制到 `~/.agents/skills/karpathy-guidelines/` +- 该 skill 本身不依赖 Playbook 文档路径重写,也不需要像 `ui-ux-pro-max` 那样额外渲染 --- diff --git a/tests/cli/test_playbook_cli.py b/tests/cli/test_playbook_cli.py index e8846bc..26377cb 100644 --- a/tests/cli/test_playbook_cli.py +++ b/tests/cli/test_playbook_cli.py @@ -27,6 +27,15 @@ def write_config(root: Path, name: str, body: str) -> Path: return config_path +def bash_path(path: Path) -> str: + resolved = path.resolve() + if sys.platform != "win32": + return resolved.as_posix() + drive = resolved.drive.rstrip(":").lower() + rest = resolved.as_posix()[2:] + return f"/mnt/{drive}{rest}" + + class PlaybookCliTests(unittest.TestCase): def assert_style_cleanup_tsl_docs_prefix( self, root: Path, agents_home: Path, docs_prefix: str @@ -275,6 +284,67 @@ skills = ["brainstorming"] self.assertEqual(result.returncode, 0) self.assertTrue(skill_file.is_file()) + def test_install_generated_thirdparty_karpathy_skill_after_sync(self): + with tempfile.TemporaryDirectory() as tmp_dir: + tmp_root = Path(tmp_dir) + mirror = tmp_root / "origin.git" + repo = tmp_root / "repo" + target = tmp_root / "agents" + + clone_mirror = subprocess.run( + ["git", "clone", "--mirror", str(ROOT), str(mirror)], + capture_output=True, + text=True, + ) + self.assertEqual(clone_mirror.returncode, 0, msg=clone_mirror.stderr) + + clone_repo = subprocess.run( + ["git", "clone", str(mirror), str(repo)], + capture_output=True, + text=True, + ) + self.assertEqual(clone_repo.returncode, 0, msg=clone_repo.stderr) + + set_remote = subprocess.run( + ["git", "-C", str(repo), "remote", "set-url", "origin", bash_path(mirror)], + capture_output=True, + text=True, + ) + self.assertEqual(set_remote.returncode, 0, msg=set_remote.stderr) + + manifest_src = ROOT / ".gitea" / "ci" / "thirdparty_skills.json" + manifest_dst = repo / ".gitea" / "ci" / "thirdparty_skills.json" + manifest_dst.write_text(manifest_src.read_text(encoding="utf-8"), encoding="utf-8") + + sync_result = subprocess.run( + ["bash", ".gitea/ci/sync_thirdparty_skills.sh"], + cwd=repo, + capture_output=True, + text=True, + ) + self.assertEqual( + sync_result.returncode, 0, msg=sync_result.stdout + sync_result.stderr + ) + + config_body = f""" +[playbook] +project_root = "{tmp_root}" +deploy_root = "{CUSTOM_DEPLOY_ROOT}" + +[install_skills] +agents_home = "{target}" +mode = "list" +skills = ["karpathy-guidelines"] +""" + config_path = tmp_root / "playbook.toml" + config_path.write_text(config_body, encoding="utf-8") + + result = run_script(repo / "scripts" / "playbook.py", "-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" diff --git a/tests/test_thirdparty_skills_pipeline.py b/tests/test_thirdparty_skills_pipeline.py index 4ac4bb6..5c4f11f 100644 --- a/tests/test_thirdparty_skills_pipeline.py +++ b/tests/test_thirdparty_skills_pipeline.py @@ -1,4 +1,8 @@ import json +import os +import shutil +import subprocess +import tempfile import unittest from pathlib import Path @@ -19,12 +23,42 @@ def load_manifest() -> dict: return json.loads(MANIFEST.read_text(encoding="utf-8")) +def bash_path(path: Path) -> str: + resolved = path.resolve() + if os.name != "nt": + return resolved.as_posix() + drive = resolved.drive.rstrip(":").lower() + rest = resolved.as_posix()[2:] + return f"/mnt/{drive}{rest}" + + +def run_command(*args: str, cwd: Path | None = None) -> subprocess.CompletedProcess[str]: + return subprocess.run( + list(args), + cwd=cwd, + capture_output=True, + text=True, + ) + + class ThirdpartySkillsPipelineTests(unittest.TestCase): - def test_manifest_declares_superpowers_and_ui_ux_pro_max(self): + def test_manifest_declares_all_thirdparty_sources(self): data = load_manifest() self.assertEqual( [entry["id"] for entry in data["sources"]], - ["superpowers", "ui-ux-pro-max"], + ["superpowers", "ui-ux-pro-max", "andrej-karpathy-skills"], + ) + + def test_karpathy_manifest_uses_copy_skill_dirs_sync_mode(self): + data = load_manifest() + karpathy = next( + item for item in data["sources"] if item["id"] == "andrej-karpathy-skills" + ) + self.assertEqual(karpathy["sync_mode"], "copy_skill_dirs") + self.assertEqual(karpathy["snapshot_dir"], "andrej-karpathy-skills") + self.assertEqual(karpathy["skills_subdir"], "skills") + self.assertEqual( + karpathy["source_list"], "codex/skills/.sources/andrej-karpathy-skills.list" ) def test_ui_ux_pro_max_uses_render_codex_skill_sync_mode(self): @@ -108,6 +142,44 @@ class ThirdpartySkillsPipelineTests(unittest.TestCase): self.assertNotIn("exclude_skill_dirs", text) self.assertNotIn("is_excluded_skill_dir", text) + def test_sync_script_generates_karpathy_outputs_in_temp_repo(self): + with tempfile.TemporaryDirectory() as tmp_dir: + tmp_root = Path(tmp_dir) + mirror = tmp_root / "origin.git" + work = tmp_root / "work" + + clone_mirror = run_command("git", "clone", "--mirror", str(ROOT), str(mirror)) + self.assertEqual(clone_mirror.returncode, 0, msg=clone_mirror.stderr) + + clone_work = run_command("git", "clone", str(mirror), str(work)) + self.assertEqual(clone_work.returncode, 0, msg=clone_work.stderr) + + set_remote = run_command( + "git", "-C", str(work), "remote", "set-url", "origin", bash_path(mirror) + ) + self.assertEqual(set_remote.returncode, 0, msg=set_remote.stderr) + + shutil.copy2(MANIFEST, work / ".gitea" / "ci" / "thirdparty_skills.json") + + sync_result = run_command("bash", ".gitea/ci/sync_thirdparty_skills.sh", cwd=work) + self.assertEqual( + sync_result.returncode, + 0, + msg=sync_result.stdout + sync_result.stderr, + ) + + generated_list = ( + work / "codex" / "skills" / ".sources" / "andrej-karpathy-skills.list" + ) + generated_skill = ( + work / "codex" / "skills" / "karpathy-guidelines" / "SKILL.md" + ) + self.assertTrue(generated_list.is_file()) + self.assertTrue(generated_skill.is_file()) + self.assertIn( + "karpathy-guidelines", generated_list.read_text(encoding="utf-8") + ) + if __name__ == "__main__": unittest.main()