✨ feat(skills): add skill_link symlink support + platform-agnostic prompt
- Add skill_link config option to create a symlink/junction from another platform's skills dir to agents_home/skills/ (e.g. ~/.claude -> ~/.agents) - On Windows, falls back to directory junction when symlink requires admin - Add _create_skills_symlink() with _is_junction() helper for Windows - Update playbook.toml.example with skill_link documentation - Fix templates/README.md prompt example to be platform-agnostic - Add 3 tests: symlink creation, idempotency, absence when unconfigured Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -583,5 +583,86 @@ project_name = "Demo"
|
||||
self.assertEqual(claude_md.read_text(encoding="utf-8"), original)
|
||||
|
||||
|
||||
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}"
|
||||
deploy_root = "{CUSTOM_DEPLOY_ROOT}"
|
||||
|
||||
[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}"
|
||||
deploy_root = "{CUSTOM_DEPLOY_ROOT}"
|
||||
|
||||
[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}"
|
||||
deploy_root = "{CUSTOM_DEPLOY_ROOT}"
|
||||
|
||||
[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()
|
||||
|
||||
Reference in New Issue
Block a user