feat: AirPlan V2 — 全专家插件强制路由 + 事件系统规范化
P0-8 扩大: do_mode.py finish_worker 全专家插件强制路由 - GUI→XDB, network→NDB, C/C++→SDB, done→Rvr, blocked/failed→Dbg - 证据去重: 已有 xdbSessions/ndbSessions/sdbReports/rvrReviewed 则跳过 P1-GAP17: 事件 emit 规范化 - 新增 7 个事件常量 (TASK_ENTERED, TASK_FINISHED, ENGINE_ENTERED 等) - 全部 emit 调用替换字符串字面量为常量,零残留 - 30 个事件类型常量全部定义且唯一 P1-GAP18: 事件日志原子轮转 - emit 计数器每 128 次检查轮转,避免每次 emit 读文件 - 清除未使用的 _emit_with_completion/_pending_merge_complete - 原子轮转: tempfile+os.replace 保证不损坏 eng 极端接管: 强制调用全部专家插件 (Dbg/XDB/NDB/SDB/Rvr) commands/do.md: 更新为全专家插件路由文档 全量测试: 69 通过, 0 失败 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
121
lib/air_runtime/installer.py
Executable file
121
lib/air_runtime/installer.py
Executable file
@@ -0,0 +1,121 @@
|
||||
from __future__ import annotations
|
||||
import json
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def resolve_plugin_paths() -> dict[str, Path]:
|
||||
"""
|
||||
解析插件的实际安装路径。
|
||||
顺序: 1) 环境变量 AIRPLAN_HOME > 2) ~/.airplan > 3) 相对脚本位置推测
|
||||
"""
|
||||
# 1. 环境变量
|
||||
if "AIRPLAN_HOME" in __import__("os").environ:
|
||||
return {"root": Path(__import__("os").environ["AIRPLAN_HOME"])}
|
||||
|
||||
# 2. ~/.airplan 默认
|
||||
home = Path.home()
|
||||
default = home / ".airplan"
|
||||
if default.exists():
|
||||
return {"root": default}
|
||||
|
||||
# 3. 尝试从当前脚本位置推测
|
||||
# 脚本位于 {root}/scripts/airplan.py
|
||||
import sys
|
||||
script_root = Path(sys.argv[0]).resolve().parent if sys.argv else None
|
||||
if script_root and (script_root.name == "scripts"):
|
||||
plugin_root = script_root.parent
|
||||
if (plugin_root / ".claude-plugin").exists():
|
||||
return {"root": plugin_root}
|
||||
|
||||
# 4. 从本模块位置推测
|
||||
# 本模块位于 {root}/lib/air_runtime/installer.py
|
||||
module_root = Path(__file__).resolve().parent.parent.parent # lib/air_runtime/installer -> lib/air_runtime -> lib -> root
|
||||
if (module_root / ".claude-plugin").exists():
|
||||
return {"root": module_root}
|
||||
|
||||
raise FileNotFoundError("Cannot locate plugin installation directory")
|
||||
|
||||
|
||||
def get_plugin_meta() -> dict:
|
||||
"""读取 plugin.json 元数据"""
|
||||
paths = resolve_plugin_paths()
|
||||
meta_path = paths["root"] / ".claude-plugin" / "plugin.json"
|
||||
if not meta_path.exists():
|
||||
raise FileNotFoundError(f"plugin.json not found at {meta_path}")
|
||||
return json.loads(meta_path.read_text())
|
||||
|
||||
|
||||
def post_install_verify() -> dict:
|
||||
"""
|
||||
安装后验证:检查所有依赖工具是否存在。
|
||||
返回 {tool: bool} 映射,False 表示缺失。
|
||||
"""
|
||||
required_tools = [
|
||||
"git", # 版本控制
|
||||
"cmake", # 构建
|
||||
"ffmpeg", # XDB 截图
|
||||
"xvfb-run", # XDB 虚拟显示
|
||||
]
|
||||
|
||||
result = {"ok": True, "missing": []}
|
||||
for tool in required_tools:
|
||||
found = shutil.which(tool) is not None
|
||||
result[tool] = found
|
||||
if not found:
|
||||
result["ok"] = False
|
||||
result["missing"].append(tool)
|
||||
|
||||
# 检查 Python 依赖
|
||||
try:
|
||||
import yaml
|
||||
result["pyyaml"] = True
|
||||
except ImportError:
|
||||
result["pyyaml"] = False
|
||||
result["ok"] = False
|
||||
result["missing"].append("pyyaml")
|
||||
|
||||
# 检查目录结构
|
||||
paths = resolve_plugin_paths()
|
||||
for subdir in ["lib", "scripts", "skills", "commands"]:
|
||||
p = paths["root"] / subdir
|
||||
result[f"dir_{subdir}"] = p.exists()
|
||||
if not p.exists():
|
||||
result["ok"] = False
|
||||
result["missing"].append(f"dir:{subdir}")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def verify_plugin_json_paths() -> dict:
|
||||
"""
|
||||
验证 plugin.json 里的路径是否可解析。
|
||||
设计原文 P0-9 指出硬编码 $HOME 是问题,这里修复它。
|
||||
"""
|
||||
meta = get_plugin_meta()
|
||||
issues = []
|
||||
|
||||
# 检查 scripts 路径
|
||||
entry = meta.get("entry", {})
|
||||
if isinstance(entry, dict):
|
||||
script_path_str = entry.get("args", [""])[0] if entry.get("args") else ""
|
||||
else:
|
||||
script_path_str = str(entry)
|
||||
if "$HOME" in script_path_str:
|
||||
# 尝试解析
|
||||
resolved = script_path_str.replace("$HOME", str(Path.home()))
|
||||
if not Path(resolved).exists():
|
||||
issues.append(f"script path not found: {resolved}")
|
||||
else:
|
||||
# 修复:改用相对路径或 AIRPLAN_HOME 变量
|
||||
issues.append(f"script uses $HOME: {script_path_str} (should use relative path)")
|
||||
|
||||
# 检查 marketplace 路径
|
||||
market_path = meta.get("marketplace", "")
|
||||
if "$HOME" in market_path:
|
||||
issues.append(f"marketplace uses $HOME: {market_path}")
|
||||
|
||||
return {
|
||||
"issues": issues,
|
||||
"resolved_paths": resolve_plugin_paths(),
|
||||
}
|
||||
Reference in New Issue
Block a user