被多个 Skill 或看板消费的连接、模型、嵌入、Claude 运行时与声音账入口 各只保留一份实现;Skill 只留 CLI/落库,看板只读 muse-db,门禁锁死跨域注入。 Co-authored-by: Cursor <cursoragent@cursor.com>
82 lines
3.4 KiB
Python
82 lines
3.4 KiB
Python
#!/usr/bin/env python3
|
||
"""门禁:Skill 不得靠 sys.path 去别人的 scripts/ 或 humanization/src 拿实现;看板不得 import Skill。
|
||
|
||
被多个 Skill 或看板消费的实现装成顶层可安装包(muse-db / muse-llm / muse-embed /
|
||
muse-claude-runtime / muse-deai),调用方 import 已安装的包。拥有该实现的 Skill 自己的
|
||
scripts/ 不受限(CLI 与库同属一个能力域)。
|
||
|
||
扫描范围只含 .claude/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 / ".claude" / "skills"
|
||
DASHBOARD = ROOT / "dashboard"
|
||
|
||
# (禁止注入的路径, 提供替代实现的包, 豁免的 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"),
|
||
("execute-claude-task/scripts", "muse-claude-runtime(import claude_runtime)",
|
||
"execute-claude-task"),
|
||
("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".claude/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 ".claude/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|claude_runtime|deai)\b", text):
|
||
offenders.append(rel)
|
||
self.assertEqual(
|
||
offenders,
|
||
[],
|
||
"看板只读共享运行时包,不得 sys.path 注入 Skill 或 import db.py:\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()
|