"""技能与文档索引行为验证。 保护:技能目录合同(SKILL.md 完整、名称唯一且全中文、frontmatter 一致) 与活跃文档索引(目录.md 标准索引、链接可达)。 测试针对现行能力与索引合同,不维护历史过程状态。 """ from __future__ import annotations import subprocess import sys from pathlib import Path import pytest 仓库根 = Path(__file__).resolve().parents[2] sys.path.insert(0, str(仓库根 / "工具")) from 维护索引 import 写入目录, 检查技能目录, 检查派生目录 # noqa: E402 def _写能力样本(根: Path, 相对: str, 身份: str, 名称: str) -> Path: import yaml 文件 = 根 / 相对 文件.parent.mkdir(parents=True, exist_ok=True) 声明 = { "id": 身份, "name": 名称, "category": "method", "contract_version": 1, "description": "合成方法", } 文件.write_text( "---\n" + yaml.safe_dump(声明, allow_unicode=True) + "---\n\n只提出有依据的规划方法。\n" ) return 文件 @pytest.mark.case_id( "NC-capability-stable-identity", environment="离线资源编译", given="显式角色frontmatter及正文", when="编译并移动物理文件", then=["稳定身份、包内路径及正文哈希不因源路径移动改变"], contract="docs/系统架构/新版设计/编排与能力资源.md", ) def test_资源按稳定身份编译且移动不改变身份__a66001(tmp_path): from 构建资源包 import 编译能力资源 文件 = tmp_path / "初始.md" 文件.write_text( "---\nid: writer\nname: 写手\ncategory: role\ncontract_version: 1\n" "description: 合成职责\n---\n\n只执行固定任务。\n" ) 目录, 资源 = 编译能力资源(tmp_path, ["初始.md"], set()) assert 目录["role"]["writer"]["name"] == "写手" assert 资源[目录["role"]["writer"]["resource_path"]] == 文件.read_bytes() 文件.rename(tmp_path / "迁移.md") 新目录, _ = 编译能力资源(tmp_path, ["迁移.md"], set()) assert 新目录["role"]["writer"]["sha256"] == 目录["role"]["writer"]["sha256"] @pytest.mark.case_id( "NC-capability-command-registry", environment="离线资源编译", given="操作技能引用具名命令", when="核对公开操作及重复身份", then=["未实现命令和重复能力身份拒绝;有效引用可进入目录"], contract="docs/系统架构/新版设计/编排与能力资源.md", ) def test_技能只声明已实现命令且重复身份拒绝__a66002(tmp_path): import pytest from 构建资源包 import 编译能力资源 文件 = tmp_path / "SKILL.md" 文件.write_text( "---\nid: inspect-task\nname: 查看任务\ncategory: operation\ncontract_version: 1\n" "description: 合成操作\ncommands: [read_task]\n---\n\n按授权读取任务。\n" ) with pytest.raises(ValueError, match="命令"): 编译能力资源(tmp_path, ["SKILL.md"], set()) 目录, _ = 编译能力资源(tmp_path, ["SKILL.md"], {"read_task"}) assert 目录["operation"]["inspect-task"]["commands"] == ["read_task"] (tmp_path / "副本.md").write_bytes(文件.read_bytes()) with pytest.raises(ValueError, match="重复"): 编译能力资源(tmp_path, ["SKILL.md", "副本.md"], {"read_task"}) @pytest.mark.case_id( "NC-capability-runtime-resource", environment="离线实际包资源", given="实际资源包中的五角色与两操作", when="经公开资源读取及角色策略接口加载", then=["按稳定身份和冻结哈希取正文,不将frontmatter写入提示,未知哈希拒绝"], contract="docs/系统架构/新版设计/编排与能力资源.md", ) def test_运行按发布目录读取角色正文和命令__a66003(): import pytest from muse.任务运行.接口 import 角色策略目录 from muse.共享.错误 import 资源缺失错误 from muse.资源加载 import 读取能力, 读取能力目录 目录 = 读取能力目录() assert set(目录["role"]) == {"writer", "planner", "extractor", "detector", "judge"} 角色 = 读取能力("role", "writer", 预期哈希=目录["role"]["writer"]["sha256"]) assert 角色["name"] == "写手" and "生成阶段" in 角色["正文"] assert not 角色["正文"].startswith("---") 策略 = 角色策略目录.从发布包() assert 策略.读取角色提示("writer") == 角色["正文"] assert len(策略.资源发布身份) == 64 操作 = 读取能力("operation", "inspect-task") assert "control_task" in 操作["commands"] with pytest.raises(资源缺失错误): 读取能力("role", "writer", 预期哈希="0" * 64) def _建技能( 根: Path, 分组: str, 名称: str, *, front名: str | None = None, 正文: str | None = None ) -> Path: 目录 = 根 / ".agent" / "skills" / 分组 / 名称 目录.mkdir(parents=True, exist_ok=True) if 正文 is None: 有效名 = front名 if front名 is not None else 名称 正文 = f"---\nname: {有效名}\ndescription: 合成技能说明\n---\n\n# {名称}\n\n方法正文。\n" (目录 / "SKILL.md").write_text(正文, encoding="utf-8") return 目录 @pytest.mark.case_id( "TC-5acb683e6f5c", environment="离线,临时资源目录", when="读取或生成登记资源及导航。", contract="docs/系统架构/新版设计/编排与能力资源.md", ) def test_missing_skills_directory_fails_closed__5acb68(tmp_path: Path) -> None: """技能根目录缺失时明确失败,不静默跳过。""" 问题 = 检查技能目录(tmp_path) assert any("不存在" in p for p in 问题), 问题 @pytest.mark.case_id( "TC-e8450b6e7c65", environment="离线,临时资源目录", when="读取或生成登记资源及导航。", contract="docs/系统架构/新版设计/编排与能力资源.md", ) def test_skill_directory_without_skill_file_fails__e8450b(tmp_path: Path) -> None: """技能目录缺少 SKILL.md 时被报告。""" 目录 = tmp_path / ".agent" / "skills" / "写作" / "无文件技能" 目录.mkdir(parents=True) (目录 / "references.md").write_text("只有资料", encoding="utf-8") 问题 = 检查技能目录(tmp_path) assert any("缺少 SKILL.md" in p for p in 问题), 问题 @pytest.mark.case_id( "TC-7b1e4d9c35a2", environment="离线,临时 git 工作树", when="读取或生成登记资源及导航。", contract="docs/系统架构/新版设计/编排与能力资源.md", ) def test_索引只覆盖会提交内容而忽略运行痕迹__7b1e4d(tmp_path: Path) -> None: """被 .gitignore 忽略的运行痕迹不进入目录派生,也不影响索引一致性。""" 根 = tmp_path subprocess.run(["git", "init", "-q"], cwd=根, check=True) (根 / ".gitignore").write_text("docs/噪音/\n", encoding="utf-8") (根 / "docs").mkdir() (根 / "docs" / "目录.md").write_text( "| 名称 | 相对地址 | 内容描述 | 使用场景 | 使用要求 |\n" "|------|----------|----------|----------|----------|\n", encoding="utf-8", ) (根 / "docs" / "真实.md").write_text("# 真实\n\n正文。\n", encoding="utf-8") (根 / "docs" / "噪音").mkdir() (根 / "docs" / "噪音" / "跑痕.json").write_text("{}\n", encoding="utf-8") 写入目录(根) 文本 = (根 / "docs" / "目录.md").read_text(encoding="utf-8") assert (根 / "docs" / "噪音" / "跑痕.json").is_file() # 痕迹在磁盘上,只是不参与派生 assert "真实.md" in 文本 assert "噪音" not in 文本 assert 检查派生目录(根) == [] def _角色字节(身份: str, 名称: str, 额外: str = "") -> bytes: return ( f"---\nid: {身份}\nname: {名称}\ncategory: role\n" f"contract_version: 1\ndescription: 合成角色\n{额外}---\n\n你是{名称},按合同执行。\n" ).encode() @pytest.mark.case_id( "TC-569f09681a64", environment="离线,临时资源目录", when="读取或生成登记资源及导航。", contract="docs/系统架构/新版设计/编排与能力资源.md", ) def test_current_role_catalog_is_exact_and_host_neutral__569f09() -> None: """已发布角色目录恰好五类且宿主中立:frontmatter 不携带任何模型绑定。""" from muse.资源加载 import 解析能力声明, 读取能力目录 目录 = 读取能力目录()["role"] assert set(目录) == {"writer", "planner", "extractor", "detector", "judge"} from muse.资源加载 import 打开资源 for 身份, 条目 in 目录.items(): 声明, 正文 = 解析能力声明(打开资源(条目["resource_path"])) assert 声明["id"] == 身份 and 声明["category"] == "role" assert "model" not in 声明 and "provider" not in 声明, f"{身份} 携带静态模型绑定" assert 正文.strip() @pytest.mark.case_id( "TC-4da9120a63cb", environment="离线,临时资源目录", when="读取或生成登记资源及导航。", contract="docs/系统架构/新版设计/编排与能力资源.md", ) def test_role_catalog_ignores_directory_index__4da912(tmp_path: Path) -> None: """能力目录只收登记文件;目录索引不是能力,不进入发布包。""" import sys as _sys _sys.path.insert(0, str(仓库根 / "工具")) from 构建资源包 import 编译能力资源 角色 = ("writer", "planner", "extractor", "detector", "judge") 名称 = { "writer": "写手", "planner": "规划员", "extractor": "知识抽取员", "detector": "检测员", "judge": "评委", } for r in 角色: (tmp_path / "角色").mkdir(exist_ok=True) (tmp_path / "角色" / f"{名称[r]}.md").write_bytes(_角色字节(r, 名称[r])) (tmp_path / "角色" / "目录.md").write_text( "| 名称 | 相对地址 | 内容描述 | 使用场景 | 使用要求 |\n" "|------|----------|----------|----------|----------|\n", encoding="utf-8", ) 目录, 资源 = 编译能力资源(tmp_path, [f"角色/{名称[r]}.md" for r in 角色], set()) assert set(目录["role"]) == set(角色) assert len(资源) == 5 # 索引文件未被扫描进包 @pytest.mark.case_id( "TC-a74ee65b8606", environment="离线,临时资源目录", when="读取或生成登记资源及导航。", contract="docs/系统架构/新版设计/编排与能力资源.md", ) def test_role_contract_is_central_and_complete__a74ee6() -> None: """角色合同与策略是唯一中心:五角色、固定模型策略与正文提示都齐备。""" import yaml from muse.任务运行.角色策略 import 角色策略目录 from muse.资源加载 import 读取能力目录 定义 = yaml.safe_load((仓库根 / "配置" / "角色策略.yaml").read_text(encoding="utf-8")) 策略 = 角色策略目录(定义, 角色资源=读取能力目录()["role"]) 合同 = (仓库根 / ".agent" / "角色" / "角色合同.md").read_text(encoding="utf-8") for 角色, 中文名 in { "writer": "网文写手", "detector": "检测员", "judge": "质量评委", "extractor": "知识抽取员", }.items(): assert 角色 in 合同 and 中文名 in 合同 for 身份 in ("writer", "planner"): assert 定义["roles"][身份]["model_policy"] == "fixed" assert 定义["roles"]["judge"]["model_policy"] == "judge-fixed" for 别名, 正名 in 定义.get("aliases", {}).items(): assert 策略.读取角色提示(别名) == 策略.读取角色提示(正名) @pytest.mark.case_id( "TC-7d0984bb6019", environment="离线,临时资源目录", when="读取或生成登记资源及导航。", contract="docs/系统架构/新版设计/编排与能力资源.md", ) def test_role_frontmatter_rejects_static_model_binding__7d0984() -> None: """角色 frontmatter 出现 model 等未登记字段直接拒绝。""" from muse.资源加载 import 解析能力声明 with pytest.raises(ValueError, match="未登记字段"): 解析能力声明(_角色字节("writer", "写手", 额外="model: claude-opus\n")) with pytest.raises(ValueError, match="未登记字段"): 解析能力声明(_角色字节("planner", "规划员", 额外="provider: primary\n")) @pytest.mark.case_id( "TC-fc9b819e3c0d", environment="离线;scripted和资源指纹不证明真实模型", when="运行新版公共场景裁决、实际CLI或安装包能力查询", then=[ "旧Skill内CLI改为共享审校CLI;安装包建立规则技能独占propose_rule_from_cases,解析动作和稳定命令身份真实存在。" ], contract="docs/系统架构/新版设计/验证设计/诊断行为场景.md", ) def test_规则建立使用共享CLI且有独立命令归属__fc9b81(): from muse.接入.cli.入口 import _解析参数 from muse.资源加载 import 读取能力 args = _解析参数(["审校", "synthetic.toml", "案例规则", "request.json"]) assert args.动作 == "案例规则" skill = 读取能力("operation", "operation.propose-review-rule") assert skill["name"] == "建立机器味规则" assert "propose_rule_from_cases" in skill["commands"] assert "capture_quality_case" not in skill["commands"]