muse-agent-example/tests/架构/test_技能与文档索引.py
zizi 8a44364539 工程(索引): 索引只维护会提交内容;修复独占锁内收集自锁
- 维护索引按 git 视角枚举(已跟踪 + 未跟踪且未忽略):被 .gitignore 忽略的运行痕迹与
  过程性检查报告不再进入目录派生或影响索引门禁;非 git 场景退回原磁盘枚举
- conftest 验证闸门在 `验证锁 --执行` 启动的会话内不再取共享锁,修复 `make 索引生成`
  在独占锁内收集用例时的自锁(原 180s 超时失败 → 现 6s 通过)
- .gitignore 登记过程性检查报告与工具会话缓存(.zcode/):可复跑,不入历史
- 新增两条测试:忽略内容不参与目录派生、独占锁内收集不自锁
2026-09-18 15:07:32 +08:00

315 lines
13 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""技能与文档索引行为验证。
保护:技能目录合同(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"]