#!/usr/bin/env python3 """门禁:Skill 不得靠 sys.path 去别人的 scripts/ 或 humanization/src 拿实现;看板不得 import Skill。 被多个 Skill 或看板消费的实现装成顶层可安装包(muse-db / muse-llm / muse-embed / muse-deai),调用方 import 已安装的包。角色执行模块 muse_role 随 muse-llm 安装。 拥有该实现的 Skill 自己的 scripts/ 不受限(CLI 与库同属一个能力域)。 扫描范围包含 `.agent/skills`、`framework`、`muse` 和当前运行时入口;测试、humanization/tests/tools/eval 不在范围内。 """ from __future__ import annotations import pathlib import re import unittest def _find_project_root(start: pathlib.Path) -> pathlib.Path: resolved = start.resolve() for directory in (resolved, *resolved.parents): if (directory / "AGENTS.md").is_file() and (directory / ".git").exists(): return directory raise RuntimeError(f"无法从 {start} 向上找到项目根") ROOT = _find_project_root(pathlib.Path(__file__)) SKILL_SCAN_ROOTS = (pathlib.Path(".agent") / "skills", pathlib.Path("muse")) DASHBOARD_ROOTS = (pathlib.Path("muse") / "authority" / "studio" / "read",) ACTIVE_RUNTIME_RELATIVE_ROOTS = ( pathlib.Path(".agent") / "skills", pathlib.Path("framework"), pathlib.Path("runtime"), pathlib.Path("muse"), pathlib.Path("muse") / "lifecycle" / "quality" / "harness", pathlib.Path("muse") / "platform" / "llm", pathlib.Path("muse") / "platform" / "embed", pathlib.Path("docs") / "write-chapter", ) # 框架派发接缝:允许直接调用 Agent 框架二进制的适配器文件。 # 新增框架适配器(codex/opencode…)必须在此登记,并保持独立于 Muse 业务。 FRAMEWORK_ADAPTER_ALLOWLIST = frozenset( {"framework/adapters/pi/runner.py", "framework/adapters/dsh/runner.py"} ) FORBIDDEN_CLAUDE_RUNTIME_TOKENS = ( "claude_runtime", "muse-claude-runtime", "execute-claude-task", "claudeExecutablePath", "claudeExecutableSha256", "claudeCliVersion", "CLAUDE_CODE_TMPDIR", "CLAUDE_CONFIG_DIR", "CLAUDE_CODE_OAUTH_TOKEN", ) # (禁止注入的路径, 提供替代实现的包, 豁免的 Skill 目录名) FORBIDDEN_PATHS = ( ("humanization/src", "muse-deai(import deai)", None), ("access-database/scripts", "muse-db(from muse_db import connect)", "access-database"), ("call-content-model/scripts", "muse-llm(import muse_llm)", "call-content-model"), ("embed-knowledge/scripts", "muse-embed(import muse_embed)", "embed-knowledge"), ("establish-voice-baseline/scripts", "muse-deai(deai.baseline / deai.load_db)", "establish-voice-baseline"), ) def _path_pattern(path: str) -> re.Pattern[str]: """同时匹配裸路径与 pathlib 拼接形态:``a/b`` 与 ``"a" / "b"``。""" head, tail = path.split("/") return re.compile(rf"""{re.escape(head)}(?:/|["']\s*/\s*["']){re.escape(tail)}""") class ImportBoundaryTest(unittest.TestCase): def test_skills_do_not_syspath_into_shared_implementations(self): for path, replacement, owner in FORBIDDEN_PATHS: with self.subTest(path=path): pattern = _path_pattern(path) offenders = [ rel for rel in self._skill_files() if not (owner and owner in pathlib.Path(rel).parts) and pattern.search((ROOT / rel).read_text(encoding="utf-8")) ] self.assertEqual( offenders, [], f"Skill 必须消费 {replacement},不得 sys.path 指向 {path}:\n" + "\n".join(offenders), ) def test_dashboard_does_not_import_skills(self): offenders: list[str] = [] for relative_root in DASHBOARD_ROOTS: for path in (ROOT / relative_root).rglob("*.py"): if path.name.startswith("test_"): continue text = path.read_text(encoding="utf-8") rel = str(path.relative_to(ROOT)) if re.search(r"sys\.path", text) and ".agent/skills" in text: offenders.append(rel) if re.search(r"^from db import|^import db\b", text, re.M): offenders.append(rel) if re.search(r"\b(muse_llm|muse_role|deai)\b", text): offenders.append(rel) self.assertEqual( offenders, [], "看板只读共享运行时包,不得 sys.path 注入 Skill 或 import db.py:\n" + "\n".join(offenders), ) def test_active_runtime_has_no_claude_cli_dependency(self): offenders: list[str] = [] suffixes = {".py", ".json", ".yaml", ".yml", ".toml", ".md"} for relative_root in ACTIVE_RUNTIME_RELATIVE_ROOTS: root = ROOT / relative_root for path in root.rglob("*"): if ( not path.is_file() or path.suffix not in suffixes or "__pycache__" in path.parts or "artifacts" in path.parts ): continue text = path.read_text(encoding="utf-8") rel = path.relative_to(ROOT).as_posix() if any(token in text for token in FORBIDDEN_CLAUDE_RUNTIME_TOKENS): offenders.append(rel) continue # 框架适配器是被批准的唯一直接调用 Agent 框架二进制的位置(07 领域框架派发接缝); # 其余任何位置 shell 调模型/框架 CLI 仍被阻断。 if rel in FRAMEWORK_ADAPTER_ALLOWLIST: continue if path.suffix == ".py" and re.search( r"(?:/bin/claude|[\"']claude[\"']\s*,\s*[\"']--version|" r"[\"'](?:pi|dsh)[\"']\s*,\s*[\"']--(?:model|profile|version)|" r"\b(?:pi|dsh)\s+--(?:model|profile|version))", text, ): offenders.append(rel) self.assertEqual( sorted(set(offenders)), [], "活跃运行链不得 shell 调模型 CLI 或依赖旧 profile 字段:\n" + "\n".join(sorted(set(offenders))), ) def test_active_runtime_has_no_embedded_api_token(self): offenders: list[str] = [] token_pattern = re.compile(r"sk-[A-Za-z0-9]{20,}") for relative_root in ACTIVE_RUNTIME_RELATIVE_ROOTS: root = ROOT / relative_root for path in root.rglob("*"): if not path.is_file() or "__pycache__" in path.parts or "artifacts" in path.parts: continue try: text = path.read_text(encoding="utf-8") except UnicodeDecodeError: continue if token_pattern.search(text): offenders.append(path.relative_to(ROOT).as_posix()) self.assertEqual( offenders, [], "活跃运行链不得内嵌 API token:\n" + "\n".join(offenders), ) def _skill_files(self) -> list[str]: files: set[str] = set() for relative_root in SKILL_SCAN_ROOTS: scan_root = ROOT / relative_root for skill_md in scan_root.rglob("SKILL.md"): for path in skill_md.parent.rglob("*.py"): files.add(path.relative_to(ROOT).as_posix()) return sorted(files) if __name__ == "__main__": unittest.main()