import json import unittest from pathlib import Path ROOT = Path(__file__).parents[2] GUIDANCE = ROOT / "plugins" / "skill" / "skills" / "guidance" INIT = ROOT / "plugins" / "knowledge" / "skills" / "init" class GuidanceInitTest(unittest.TestCase): """验证规范检索和项目初始化之间的职责边界。""" def test_guidance_is_read_only_and_uses_progressive_sources(self) -> None: """检索 Skill 应声明项目优先、渐进读取且不隐式初始化。""" content = (GUIDANCE / "SKILL.md").read_text(encoding="utf-8") self.assertIn("以只读方式", content) self.assertIn("不得先递归加载整个知识库", content) self.assertIn("不得在检索过程中隐式初始化", content) self.assertIn(".craftkit/project.json", content) def test_public_baseline_routes_frontend_and_backend(self) -> None: """公共基线应提供前后端入口,并让索引引用实际规则文件。""" root = GUIDANCE / "references" / "guidance" index = (root / "index.md").read_text(encoding="utf-8") self.assertIn("frontend/index.md", index) self.assertIn("backend/index.md", index) self.assertEqual(6, len(list((root / "frontend").glob("*.md")))) self.assertEqual(6, len(list((root / "backend").glob("*.md")))) sources = (root / "sources.md").read_text(encoding="utf-8") for authority in ("RFC 9110", "WCAG 2.2", "OWASP", "MDN Fetch API"): self.assertIn(authority, sources) def test_framework_profiles_are_selected_by_project_version(self) -> None: """普通任务只能使用当前 profile,且不存在的 profile 不得被猜测。""" routing = (GUIDANCE / "references" / "profile-routing.md").read_text( encoding="utf-8" ) self.assertIn("普通开发任务只加载当前 profile", routing) self.assertIn("升级、迁移或版本比较", routing) self.assertIn("不默认最新版本", routing) self.assertIn("不创建空目录", routing) init = (INIT / "SKILL.md").read_text(encoding="utf-8") config = (INIT / "references" / "project-config.md").read_text( encoding="utf-8" ) self.assertIn("精确版本、公共 profile", init) for field in ('"name"', '"version"', '"profile"'): self.assertIn(field, config) def test_init_supports_existing_and_new_projects(self) -> None: """初始化 Skill 应同时包含成熟项目与空项目流程。""" content = (INIT / "SKILL.md").read_text(encoding="utf-8") self.assertIn("成熟项目", content) self.assertIn("空项目", content) self.assertIn("参考项目", content) self.assertTrue((INIT / "references" / "existing.md").is_file()) self.assertTrue((INIT / "references" / "new.md").is_file()) def test_project_template_has_safe_shared_fields(self) -> None: """项目模板应包含检索入口,且不得预置内部标识或机器路径。""" path = INIT / "assets" / "project.json" payload = json.loads(path.read_text(encoding="utf-8")) self.assertEqual(1, payload["schemaVersion"]) self.assertIn(payload["initialization"]["mode"], {"existing", "new"}) self.assertEqual([], payload["dependencies"]["internal"]) self.assertEqual([], payload["frontend"]["componentLibraries"]) self.assertEqual([], payload["frontend"]["componentRoots"]) self.assertEqual([], payload["frontend"]["documentation"]) self.assertEqual( ".craftkit/standards/index.md", payload["guidance"]["standardsIndex"], ) serialized = json.dumps(payload, ensure_ascii=False) self.assertNotIn(":\\", serialized) def test_init_collects_frontend_component_metadata(self) -> None: """初始化应记录组件库证据和入口,但不能默认选择组件库。""" content = "\n".join( path.read_text(encoding="utf-8") for path in ( INIT / "SKILL.md", INIT / "references" / "project-config.md", INIT / "references" / "existing.md", INIT / "references" / "new.md", ) ) self.assertIn("componentLibraries", content) self.assertIn("组件根", content) self.assertIn("不默认选择流行方案", content) self.assertTrue((INIT / "assets" / "frontend-components.md").is_file()) def test_local_directories_are_the_only_nested_ignores(self) -> None: """初始化模板只排除本地状态和缓存,不得排除共享资料。""" rules = (INIT / "assets" / "craftkit.gitignore").read_text( encoding="utf-8" ) active = [line for line in rules.splitlines() if line and not line.startswith("#")] self.assertEqual(["/local/", "/cache/"], active) if __name__ == "__main__": unittest.main()