📦 deps(skills): add karpathy thirdparty sync

This commit is contained in:
csh 2026-04-23 09:31:45 +08:00
parent 96b705b00b
commit 08ca87b74c
4 changed files with 161 additions and 2 deletions

View File

@ -22,6 +22,15 @@
"template_root": "src/ui-ux-pro-max/templates", "template_root": "src/ui-ux-pro-max/templates",
"data_dir": "src/ui-ux-pro-max/data", "data_dir": "src/ui-ux-pro-max/data",
"scripts_dir": "src/ui-ux-pro-max/scripts" "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"
} }
] ]
} }

View File

@ -158,6 +158,14 @@ python <deploy_root>/scripts/playbook.py -config playbook.toml
- `superpowers.list` - `superpowers.list`
- `ui-ux-pro-max.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` 那样额外渲染
--- ---

View File

@ -27,6 +27,15 @@ def write_config(root: Path, name: str, body: str) -> Path:
return config_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): class PlaybookCliTests(unittest.TestCase):
def assert_style_cleanup_tsl_docs_prefix( def assert_style_cleanup_tsl_docs_prefix(
self, root: Path, agents_home: Path, docs_prefix: str self, root: Path, agents_home: Path, docs_prefix: str
@ -275,6 +284,67 @@ skills = ["brainstorming"]
self.assertEqual(result.returncode, 0) self.assertEqual(result.returncode, 0)
self.assertTrue(skill_file.is_file()) 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): def test_install_skills_rejects_removed_tsl_guide(self):
with tempfile.TemporaryDirectory() as tmp_dir: with tempfile.TemporaryDirectory() as tmp_dir:
target = Path(tmp_dir) / "agents" target = Path(tmp_dir) / "agents"

View File

@ -1,4 +1,8 @@
import json import json
import os
import shutil
import subprocess
import tempfile
import unittest import unittest
from pathlib import Path from pathlib import Path
@ -19,12 +23,42 @@ def load_manifest() -> dict:
return json.loads(MANIFEST.read_text(encoding="utf-8")) 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): 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() data = load_manifest()
self.assertEqual( self.assertEqual(
[entry["id"] for entry in data["sources"]], [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): 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("exclude_skill_dirs", text)
self.assertNotIn("is_excluded_skill_dir", 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__": if __name__ == "__main__":
unittest.main() unittest.main()