P1-19.1: Arc 边界测试强制 - TaskNode 新增 test_required 字段 - _inject_boundary_tests() 为每个模块注入接口测试和单元测试任务 - Done When 验证必须包含"测试通过" P1-19.2: AirRvr 高风险审计 - 新增 HighRiskAudit, HighRiskFinding 数据类 - ReviewReport 新增 highRiskAudit 字段,含 lifecycle/nullPointer/danglingPointer/exceptionSafety/concurrency + overallRisk + deliveryVerdict - 序列化/反序列化支持 P1-19.3: block-release 集成 - dispatch_worker_group() 派发前扫描最新审查报告 - deliveryVerdict=block-release 时阻止所有后续派发 - 记录 eng.blocked 事件 P1-20: frontend-design Skill 集成 - is_ui_task() UI 任务检测 - ensure_frontend_design_skill() 自动安装 Skill - route_ui_task() UI 任务路由决策 - enter_worker() 集成 UI 检测,skill 不可用时阻止执行 - commands/do.md 更新 UI 处理说明 - SKILL.md 新增 INV-12/INV-13/INV-14 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
122 lines
3.8 KiB
Python
122 lines
3.8 KiB
Python
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(),
|
||
}
|