129 lines
6.0 KiB
Python
129 lines
6.0 KiB
Python
"""Tests for registry.py: records, members, single-skill modules, unregistered skills, symlink dedupe."""
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
SCRIPT = Path(__file__).resolve().parents[1] / "registry.py"
|
|
|
|
|
|
def write(path: Path, text: str) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(text, encoding="utf-8")
|
|
|
|
|
|
def make_project(root: Path) -> Path:
|
|
project = root / "project"
|
|
agents = project / ".agents" / "skills"
|
|
(project / "_bmad").mkdir(parents=True)
|
|
# core record
|
|
write(agents / "bmod-core-tools" / "SKILL.md", "---\nname: bmod-core-tools\ndescription: x\n---\n")
|
|
write(
|
|
agents / "bmod-core-tools" / "bmod.toml",
|
|
'[bmod]\ncode = "core-tools"\nversion = "6.13.0"\nupdate_source = "github:bmad-code-org/BMAD-METHOD/skills"\nskills = ["bmad"]\n',
|
|
)
|
|
write(agents / "bmad" / "SKILL.md", "---\nname: bmad\ndescription: x\n---\n")
|
|
write(
|
|
agents / "bmad" / "bmod.toml",
|
|
'[skill]\nbmod = "bmod-core-tools"\nsource = "github:bmad-code-org/BMAD-METHOD/skills"\n',
|
|
)
|
|
# an org module with an agent member and help
|
|
write(agents / "bmod-acme" / "SKILL.md", "---\nname: bmod-acme\ndescription: x\n---\n")
|
|
write(
|
|
agents / "bmod-acme" / "bmod.toml",
|
|
'[bmod]\ncode = "acme"\nversion = "1.0.0"\nupdate_source = "github:acme/skills"\nskills = ["acme-release-notes"]\n',
|
|
)
|
|
write(agents / "bmod-acme" / "help" / "help.md", "# Acme\n")
|
|
write(agents / "bmod-acme" / "roster.toml", "[[members]]\ncode = 'acme-agent-scout'\n")
|
|
write(agents / "acme-release-notes" / "SKILL.md", "---\nname: acme-release-notes\ndescription: x\n---\n")
|
|
write(agents / "acme-release-notes" / "bmod.toml", '[skill]\nbmod = "bmod-acme"\nsource = "github:acme/skills"\n')
|
|
write(agents / "acme-agent-scout" / "SKILL.md", "---\nname: acme-agent-scout\ndescription: x\n---\n")
|
|
write(agents / "acme-agent-scout" / "bmod.toml", '[skill]\nbmod = "bmod-acme"\nsource = "github:acme/skills"\n')
|
|
# a single-skill module
|
|
write(agents / "doc-standards" / "SKILL.md", "---\nname: doc-standards\ndescription: x\n---\n")
|
|
write(
|
|
agents / "doc-standards" / "bmod.toml",
|
|
'[bmod]\ncode = "docstd"\nversion = "0.1.0"\nupdate_source = "github:me/doc-standards"\n\n[skill]\n',
|
|
)
|
|
write(agents / "doc-standards" / "help" / "help.md", "# Doc standards\n")
|
|
# wired to BMad, registered nowhere
|
|
write(agents / "random-generator" / "SKILL.md", "---\nname: random-generator\ndescription: x\n---\n")
|
|
write(agents / "random-generator" / "customize.toml", "[workflow]\n")
|
|
write(
|
|
agents / "joke" / "SKILL.md",
|
|
"---\nname: joke\ndescription: x\n---\nRun `uv run {project-root}/_bmad/scripts/resolve_config.py`.\n",
|
|
)
|
|
# plain, outside BMad
|
|
write(agents / "plain" / "SKILL.md", "---\nname: plain\ndescription: x\n---\nJust do it.\n")
|
|
# .claude/skills mirrors .agents/skills through symlinks
|
|
claude = project / ".claude" / "skills"
|
|
claude.mkdir(parents=True)
|
|
for folder in agents.iterdir():
|
|
os.symlink(Path("..") / ".." / ".agents" / "skills" / folder.name, claude / folder.name)
|
|
return project
|
|
|
|
|
|
def run(project: Path, *extra: str) -> dict:
|
|
res = subprocess.run(
|
|
[sys.executable, str(SCRIPT), "--project-root", str(project), *extra], capture_output=True, text=True
|
|
)
|
|
assert res.returncode == 0, res.stderr
|
|
return json.loads(res.stdout)
|
|
|
|
|
|
class RegistryTest(unittest.TestCase):
|
|
def test_records_members_and_unregistered(self):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
project = make_project(Path(tmp))
|
|
roots = [str(project / ".agents" / "skills"), str(project / ".claude" / "skills")]
|
|
data = run(project, "--root", roots[0], "--root", roots[1])
|
|
self.assertEqual(data["bmad"], {"present": True, "version": "6.13.0"})
|
|
self.assertFalse(data["skills_repo"])
|
|
by_code = {r["code"]: r for r in data["records"]}
|
|
self.assertEqual(sorted(by_code), ["acme", "core-tools", "docstd"])
|
|
acme = by_code["acme"]
|
|
self.assertEqual(acme["skills"], ["acme-agent-scout", "acme-release-notes"])
|
|
self.assertEqual(acme["prefix"], "acme-")
|
|
self.assertTrue(acme["has_help"])
|
|
self.assertTrue(acme["has_roster"])
|
|
self.assertFalse(acme["single_skill"])
|
|
self.assertEqual(acme["scope"], "project")
|
|
doc = by_code["docstd"]
|
|
self.assertTrue(doc["single_skill"])
|
|
self.assertEqual(doc["skills"], [])
|
|
self.assertEqual(doc["prefix"], "docstd-")
|
|
self.assertEqual(by_code["core-tools"]["prefix"], "bmad-")
|
|
self.assertEqual(sorted(u["skill"] for u in data["unregistered"]), ["joke", "random-generator"])
|
|
# the symlinked copies did not double anything
|
|
self.assertEqual(len(data["records"]), 3)
|
|
self.assertEqual(len(data["unregistered"]), 2)
|
|
|
|
def test_empty_project(self):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
project = Path(tmp) / "empty"
|
|
project.mkdir()
|
|
data = run(project, "--root", str(project / "nowhere"))
|
|
self.assertEqual(data["bmad"], {"present": False, "version": None})
|
|
self.assertEqual(data["records"], [])
|
|
self.assertEqual(data["unregistered"], [])
|
|
|
|
def test_skills_repo_detected(self):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
project = Path(tmp) / "repo"
|
|
write(project / "skills" / "bmod-x" / "SKILL.md", "---\nname: bmod-x\ndescription: x\n---\n")
|
|
write(
|
|
project / "skills" / "bmod-x" / "bmod.toml",
|
|
'[bmod]\ncode = "x"\nversion = "1"\nupdate_source = "github:a/b"\n',
|
|
)
|
|
data = run(project, "--root", str(project / "skills"))
|
|
self.assertTrue(data["skills_repo"])
|
|
self.assertEqual(data["records"][0]["code"], "x")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|