muse-agent-example/tests/architecture/test_import_boundaries.py
zizi 091b66a9bb 重构: 收敛 Agent/Skill 运行时与创作质量闭环
将角色与 Skill 从 .claude 迁入 .agent,移除 Claude CLI 运行时并接入固定 Opus 角色 profile、完整 schema、预算 deadline、raw 与回执证据链。

同步拆分 Skill 职责、复利 lesson、Gate 回放、Dashboard 人审入口、数据库登记和机械门禁;候选设计正文不包含在本提交中。
2026-08-22 02:12:32 +08:00

147 lines
5.8 KiB
Python
Raw 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.

#!/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 与 dashboard;测试、humanization/tests/tools/eval 不在范围内。
"""
from __future__ import annotations
import pathlib
import re
import unittest
ROOT = pathlib.Path(__file__).resolve().parents[2]
SKILLS = ROOT / ".agent" / "skills"
DASHBOARD = ROOT / "dashboard"
ACTIVE_RUNTIME_ROOTS = (
ROOT / ".agent" / "skills",
ROOT / "dashboard",
ROOT / "harness",
ROOT / "muse-llm",
ROOT / "muse-embed",
ROOT / "docs" / "write-chapter",
)
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)
prefix = f".agent/skills/{owner}/" if owner else None
offenders = [
rel for rel in self._skill_files()
if not (prefix and rel.startswith(prefix))
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 path in DASHBOARD.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 root in ACTIVE_RUNTIME_ROOTS:
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")
if any(token in text for token in FORBIDDEN_CLAUDE_RUNTIME_TOKENS):
offenders.append(path.relative_to(ROOT).as_posix())
continue
if re.search(
r"(?:/bin/claude|[\"']claude[\"']\s*,\s*[\"']--version|"
r"[\"']pi[\"']\s*,|\bpi\s+--model)",
text,
):
offenders.append(path.relative_to(ROOT).as_posix())
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 root in ACTIVE_RUNTIME_ROOTS:
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]:
return sorted(p.relative_to(ROOT).as_posix() for p in SKILLS.rglob("*.py"))
if __name__ == "__main__":
unittest.main()