muse-agent-example/工具/效果依赖.py
zizi 0658d178ad 工程底座:单一生成入口、验证期写闸门与并行分片
- Makefile:新增 验收数据库(生成→检查→库层全量,前置校验隔离库连接串)、数据库分片、浏览器测试三个入口;
  格式写入 末尾就地重新生成;数据库测试 与 验收数据库 统一排除 浏览器/网络;快检 不再静默跳过类型门;
  前端旅程 预检补齐四个必需变量;pytest 目标改用仓内解释器(uv run 在嵌套检出会解析到外层环境)。
- 工具/验证锁.py:验证会话持共享锁,写入口用 --执行 在独占锁内落盘,生成/格式写入/索引生成走同一闸门。
- 工具/并行数据库测试.py:按文件分片并行,缺连接串在采集前拒绝。
- 工具/构建编排.py、环境预检.py、构建资源包.py、维护索引.py 与上述口径对齐。
- .gitignore / CI / README / AGENTS:忽略构建产物、CI 与 Makefile 单一口径、README 按实际实现陈述、AGENTS 补日常入口与验证纪律。
2026-09-18 01:14:50 +08:00

132 lines
5.3 KiB
Python

"""从实际业务入口递归提取本仓 Python 导入闭包,用于效果资质投影。
入口是能力合同,不是待维护的文件白名单。未知动态导入拒绝封闭;类型专用导入
不参与运行闭包。资源由构建器按生成阶段传入,不按文件名猜测是否影响效果。
"""
from __future__ import annotations
import ast
import hashlib
from pathlib import Path
能力入口 = {
"writer_generation": ["muse.编排.生成正文", "muse.编排.执行评测"],
"literary_judging": [
"muse.编排.执行评测",
"muse.效果评测.文学评分",
"muse.效果评测.启用判据",
],
"semantic_detection": [
"muse.编排.审校修订",
"muse.编排.执行评测",
"muse.效果评测.语义检测",
],
}
def _运行节点(树: ast.AST):
"""保留函数内部实际导入;跳过静态类型检查分支。"""
yield 树
for 子 in ast.iter_child_nodes(树):
if isinstance(子, ast.If) and (
isinstance(子.test, ast.Name)
and 子.test.id == "TYPE_CHECKING"
or isinstance(子.test, ast.Attribute)
and 子.test.attr == "TYPE_CHECKING"
):
for 节点 in 子.orelse:
yield from _运行节点(节点)
else:
yield from _运行节点(子)
def _闭包(源码根: Path, 入口: list[str]) -> tuple[dict[str, str], list[str]]:
模块文件 = {}
for 文件 in 源码根.rglob("*.py"):
if "__pycache__" in 文件.parts:
continue
片段 = list(文件.relative_to(源码根).with_suffix("").parts)
if 片段[-1] == "__init__":
片段.pop()
模块文件[".".join(片段)] = 文件
结果: dict[str, str] = {}
未封闭: set[str] = set()
待查 = list(入口)
while 待查:
名 = 待查.pop()
if 名 in 结果:
continue
文件 = 模块文件.get(名)
if 文件 is None:
未封闭.add(f"缺本仓模块:{名}")
continue
树 = ast.parse(文件.read_text(encoding="utf-8"))
结果[名] = hashlib.sha256(ast.dump(树, include_attributes=False).encode()).hexdigest()
依赖: set[str] = set()
节点们 = list(_运行节点(树))
动态名称 = {"__import__"}
for 节点 in 节点们:
if isinstance(节点, ast.ImportFrom) and 节点.module == "importlib":
动态名称.update(v.asname or v.name for v in 节点.names if v.name == "import_module")
for 节点 in 节点们:
if isinstance(节点, ast.Import):
依赖.update(v.name for v in 节点.names)
elif isinstance(节点, ast.ImportFrom):
基 = 节点.module or ""
if 节点.level:
包 = 名.split(".") if 文件.name == "__init__.py" else 名.split(".")[:-1]
基 = ".".join(包[: len(包) - 节点.level + 1] + ([基] if 基 else []))
依赖.add(基)
依赖.update(f"{基}.{v.name}" for v in 节点.names if f"{基}.{v.name}" in 模块文件)
elif isinstance(节点, ast.Call):
函数 = 节点.func
动态 = isinstance(函数, ast.Name) and 函数.id in 动态名称
动态 = (
动态
or isinstance(函数, ast.Attribute)
and 函数.attr in {"import_module", "__import__"}
)
if 动态:
if (
节点.args
and isinstance(节点.args[0], ast.Constant)
and isinstance(节点.args[0].value, str)
and not 节点.args[0].value.startswith(".")
):
依赖.add(节点.args[0].value)
else:
未封闭.add(f"动态导入:{名}:{节点.lineno}")
if isinstance(函数, ast.Name) and 函数.id in {"exec", "eval"}:
未封闭.add(f"动态代码:{名}:{节点.lineno}")
# 导入子模块也会执行各级包的 __init__。
依赖.update(".".join(名.split(".")[:i]) for i in range(1, len(名.split("."))))
for 目标 in sorted(依赖):
if 目标 == "muse" or 目标.startswith("muse."):
if 目标 in 模块文件:
if 目标 not in 结果:
待查.append(目标)
else:
未封闭.add(f"缺本仓模块:{目标}")
return dict(sorted(结果.items())), sorted(未封闭)
def 构建效果依赖(根: Path, 资源: dict[str, str], 分组: dict[str, list[str]]) -> dict:
能力 = {}
for 名, 根模块 in 能力入口.items():
入口 = sorted(set(根模块 + ["muse.编排.运行装配"]))
代码, 未封闭 = _闭包(根 / "src", 入口)
能力[名] = {
"roots": 入口,
"code": 代码,
"resources": dict(sorted(资源.items())),
"resource_groups": 分组,
"closed": not 未封闭,
"unresolved": 未封闭,
}
return {
"version": "effect-dependencies-v1",
"dependency_lock_sha256": hashlib.sha256((根 / "uv.lock").read_bytes()).hexdigest(),
"capabilities": 能力,
}