214 lines
8.4 KiB
Python
214 lines
8.4 KiB
Python
#!/usr/bin/env python3
|
||
"""门禁:中文技能目录必须与磁盘、frontmatter、skills.json 一致。
|
||
|
||
两份目录是生成物(`muse/lifecycle/quality/harness/skills_index.py`):技能名与技能文件来自磁盘和
|
||
`muse/lifecycle/quality/harness/manifests/skills.json`,技能说明来自 SKILL.md frontmatter(SoT)。
|
||
技能增删改名漏同步、手改目录造成的漂移都会被本测试拒绝;修复方式是
|
||
`.venv/bin/python muse/lifecycle/quality/harness/skills_index.py --write`。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import pathlib
|
||
import subprocess
|
||
import sys
|
||
import tempfile
|
||
import unittest
|
||
|
||
ROOT = pathlib.Path(__file__).resolve().parents[2]
|
||
|
||
|
||
class SkillsIndexConsistencyTest(unittest.TestCase):
|
||
def test_index_matches_disk_and_manifest(self) -> None:
|
||
result = subprocess.run(
|
||
[
|
||
sys.executable,
|
||
str(ROOT / "muse" / "lifecycle" / "quality" / "harness" / "skills_index.py"),
|
||
"--root",
|
||
str(ROOT),
|
||
"--check",
|
||
],
|
||
capture_output=True,
|
||
text=True,
|
||
)
|
||
self.assertEqual(
|
||
result.returncode,
|
||
0,
|
||
"技能目录与事实漂移,请运行 muse/lifecycle/quality/harness/skills_index.py --write 重新生成:\n"
|
||
+ result.stdout
|
||
+ result.stderr,
|
||
)
|
||
|
||
def test_write_uses_chinese_index_paths_and_headers(self) -> None:
|
||
with tempfile.TemporaryDirectory() as temporary:
|
||
root = pathlib.Path(temporary)
|
||
skill = root / ".agent" / "skills" / "规划" / "故事结构" / "SKILL.md"
|
||
skill.parent.mkdir(parents=True)
|
||
skill.write_text(
|
||
"---\nname: 故事结构\ndescription: 搭建故事结构。\n---\n# 故事结构\n",
|
||
encoding="utf-8",
|
||
)
|
||
business_skill = (
|
||
root
|
||
/ "muse"
|
||
/ "platform"
|
||
/ "llm"
|
||
/ "skills"
|
||
/ "调用内容模型"
|
||
/ "SKILL.md"
|
||
)
|
||
business_skill.parent.mkdir(parents=True)
|
||
business_skill.write_text(
|
||
"---\nname: 调用内容模型\ndescription: 调用受控内容模型。\n---\n"
|
||
"# 调用内容模型\n",
|
||
encoding="utf-8",
|
||
)
|
||
manifest = (
|
||
root
|
||
/ "muse"
|
||
/ "lifecycle"
|
||
/ "quality"
|
||
/ "harness"
|
||
/ "manifests"
|
||
/ "skills.json"
|
||
)
|
||
manifest.parent.mkdir(parents=True)
|
||
manifest.write_text(
|
||
json.dumps(
|
||
{
|
||
"schema_version": 3,
|
||
"skill_name_policy": "chinese_only",
|
||
"skills": [
|
||
{
|
||
"name": "故事结构",
|
||
"lifecycle": "planning",
|
||
"invocation": "model_routed",
|
||
"side_effects": ["none"],
|
||
"compounding": "partial",
|
||
"contract_owner": "规划与作品基础",
|
||
"collaborates_with": [],
|
||
"skill_path": ".agent/skills/规划/故事结构/SKILL.md",
|
||
},
|
||
{
|
||
"name": "调用内容模型",
|
||
"lifecycle": "platform",
|
||
"invocation": "orchestrated",
|
||
"side_effects": ["external_call"],
|
||
"compounding": "none",
|
||
"contract_owner": "平台底座",
|
||
"collaborates_with": [],
|
||
"skill_path": (
|
||
"muse/platform/llm/skills/调用内容模型/SKILL.md"
|
||
),
|
||
}
|
||
],
|
||
},
|
||
ensure_ascii=False,
|
||
indent=2,
|
||
)
|
||
+ "\n",
|
||
encoding="utf-8",
|
||
)
|
||
|
||
result = subprocess.run(
|
||
[
|
||
sys.executable,
|
||
str(ROOT / "muse" / "lifecycle" / "quality" / "harness" / "skills_index.py"),
|
||
"--root",
|
||
str(root),
|
||
"--write",
|
||
],
|
||
capture_output=True,
|
||
text=True,
|
||
)
|
||
|
||
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
||
method_root = root / ".agent" / "skills" / "目录.md"
|
||
method_group = root / ".agent" / "skills" / "规划" / "目录.md"
|
||
business_index = root / "muse" / "技能目录.md"
|
||
self.assertTrue(method_root.is_file())
|
||
self.assertTrue(method_group.is_file())
|
||
self.assertTrue(business_index.is_file())
|
||
self.assertFalse((root / ".agent" / "skills" / "_index.md").exists())
|
||
self.assertFalse((root / "muse" / "_skills_index.md").exists())
|
||
root_text = method_root.read_text(encoding="utf-8")
|
||
self.assertIn("| 名称 | 相对地址 | 内容描述 | 使用场景 | 使用要求 |", root_text)
|
||
self.assertIn("| 规划类技能 | [目录.md](规划/目录.md) |", root_text)
|
||
group_text = method_group.read_text(encoding="utf-8")
|
||
self.assertIn(
|
||
"| 名称 | 相对地址 | 内容描述 | 使用场景 | 使用要求 |",
|
||
group_text,
|
||
)
|
||
self.assertIn(
|
||
"| 故事结构 | [SKILL.md](故事结构/SKILL.md) | 搭建故事结构。 |",
|
||
group_text,
|
||
)
|
||
business_text = business_index.read_text(encoding="utf-8")
|
||
self.assertIn(
|
||
"| 调用内容模型 | "
|
||
"[SKILL.md](platform/llm/skills/调用内容模型/SKILL.md) |",
|
||
business_text,
|
||
)
|
||
|
||
def test_write_rejects_legacy_english_method_category(self) -> None:
|
||
with tempfile.TemporaryDirectory() as temporary:
|
||
root = pathlib.Path(temporary)
|
||
skill = root / ".agent" / "skills" / "planning" / "story" / "SKILL.md"
|
||
skill.parent.mkdir(parents=True)
|
||
skill.write_text(
|
||
"---\nname: story\ndescription: 旧分类夹具。\n---\n# Story\n",
|
||
encoding="utf-8",
|
||
)
|
||
manifest = (
|
||
root
|
||
/ "muse"
|
||
/ "lifecycle"
|
||
/ "quality"
|
||
/ "harness"
|
||
/ "manifests"
|
||
/ "skills.json"
|
||
)
|
||
manifest.parent.mkdir(parents=True)
|
||
manifest.write_text(
|
||
json.dumps(
|
||
{
|
||
"schema_version": 3,
|
||
"skill_name_policy": "transitional",
|
||
"skills": [
|
||
{
|
||
"name": "story",
|
||
"lifecycle": "planning",
|
||
"invocation": "model_routed",
|
||
"side_effects": ["none"],
|
||
"compounding": "partial",
|
||
"contract_owner": "测试夹具",
|
||
"collaborates_with": [],
|
||
"skill_path": ".agent/skills/planning/story/SKILL.md",
|
||
}
|
||
],
|
||
},
|
||
ensure_ascii=False,
|
||
indent=2,
|
||
)
|
||
+ "\n",
|
||
encoding="utf-8",
|
||
)
|
||
|
||
result = subprocess.run(
|
||
[
|
||
sys.executable,
|
||
str(ROOT / "muse" / "lifecycle" / "quality" / "harness" / "skills_index.py"),
|
||
"--root",
|
||
str(root),
|
||
"--write",
|
||
],
|
||
capture_output=True,
|
||
text=True,
|
||
)
|
||
|
||
self.assertEqual(result.returncode, 2, result.stdout + result.stderr)
|
||
self.assertIn("未登记分类目录:planning", result.stdout)
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main()
|