#!/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()