From 0d6eb1a4bb1a370ebdf6435ae433b211b4762002 Mon Sep 17 00:00:00 2001 From: AirPlan Team Date: Wed, 10 Jun 2026 16:26:00 +0800 Subject: [PATCH] Add design document Co-Authored-By: Claude Sonnet 4.6 --- airplanV2-Qwen3.7-Max设计.md | 1477 ++++++++++++++++++++++++++++++++++ 1 file changed, 1477 insertions(+) create mode 100644 airplanV2-Qwen3.7-Max设计.md diff --git a/airplanV2-Qwen3.7-Max设计.md b/airplanV2-Qwen3.7-Max设计.md new file mode 100644 index 0000000..1150def --- /dev/null +++ b/airplanV2-Qwen3.7-Max设计.md @@ -0,0 +1,1477 @@ +# AirPlan V2 设计文档 + +> **版本**: Draft 0.1 +> **日期**: 2026-06-09 +> **基线**: AirPlan V1 (air-suite-20260518, air_runtime ~4,500 行, 8 插件) +> **状态**: 待评审 + +--- + +## 0. 文档目的 + +本文档记录 AirPlan V1 在真实工程项目(DecodePlayer 系列, AirCoding V1.0.0 Alpha)中暴露的系统性缺陷,并定义 V2 的架构改进方向、新增组件设计和分阶段实施计划。 + +V2 的核心命题:**V1 证明了制品驱动 + 上下文隔离 + 波次并行的架构方向成立;V2 要解决可靠性、可观测性和规模化问题。** + +--- + +## 1. V1 问题诊断 + +### 1.1 缺陷分级 + +#### P0 — 已造成实际损失 + +| ID | 缺陷 | 位置 | 影响 | 根因 | +|----|------|------|------|------| +| P0-1 | AirXDB 假阳性阻塞 | `airxdb_runtime.py` `ensure_xdb_sessions_for_result()` | 11+ 任务触发虚假修复循环,每次需人工覆盖 | 证据门控无任务类型感知 | +| P0-2 | 部署验证缺口 | `contracts.py` `validate_for_finalize()` | T-028b 发现 4 个 "done" 任务未实际部署,延迟发布一整天 | 验证只检查结构完整性,不检查部署一致性 | +| P0-3 | 非原子写入 | 全部 `_json_dump` (5 份) | 进程崩溃时 state.json 截断,下次加载 JSONDecodeError | `path.write_text()` 直接覆盖,无 tempfile + rename | +| P0-4 | 零并发控制 | 整个 `air_runtime/` | 两个 Worker 同时完成时 todo.md 读-改-写竞态 | 无任何锁、互斥量或原子操作 | +| P0-5 | AirArc 被 plan 模式劫持 | AirArc SKILL.md / 命令文件 | 架构器频繁被 Agent 内置 plan mode 接管,偏离架构规划职责 | 缺乏 plan mode 阻断机制,SKILL.md 指令不够强硬 | +| P0-6 | AirEng 停下来问而不自主决策 | AirEng SKILL.md / 命令文件 | 调度器用英文反复询问用户确认,而非中文自主推进开发进度 | SKILL.md 未强制"以推进为目标自行决策",语言未锁定中文 | +| P0-7 | AirEng 无子线程状态轮询 | `engine.py` `monitor_engine()` | 子线程卡死后调度器无限等待,必须用户手动发现并告知 | 轮询逻辑依赖 Agent 自觉执行,无硬编码的定时轮询循环 | +| P0-8 | AirDo 不调用 AirDbg | AirDo SKILL.md / `worker.py` `finish_worker()` | 执行器遇到问题或验收时几乎不触发调试,直接报 blocked 或 false-done | 自动路由为建议性而非强制,Worker 倾向于跳过调试直接返回 | +| P0-9 | 安装器脚本路径错误 | 安装脚本 / 插件注册逻辑 | 安装后插件无法识别,AI 修复后可识别但脚本执行失败,路径不正确 | 安装器未正确解析插件脚本的绝对路径,注册的命令路径与实际文件位置不匹配 | +| P0-10 | AirEng 偏离调度亲自写代码 | AirEng SKILL.md / 命令文件 | 调度引擎频繁偏离调度职责自己编写代码,破坏隔离架构。极端情况(子代理循环阻塞需接手合并)允许少量修改,但日常调度中不应发生 | SKILL.md 未明确区分"仅调度"与"极端接管"的边界,无工具限制约束 | + +#### P1 — 限制可靠性与可维护性 + +| ID | 缺陷 | 位置 | 影响 | +|----|------|------|------| +| P1-1 | 硬编码开发者路径 | `debug_runtime.py:130`, `airxdb_runtime.py:156` | `C:\Users\20392\...` 在其他机器静默失败 | +| P1-2 | `_json_dump`/`_json_load` 5 份重复 | engine, worker, airxdb, debug, repair | 修一处漏四处 | +| P1-3 | `_ordered_unique` 4 份重复 | airxdb, debug, repair, contracts | 同上 | +| P1-4 | policy normalization 3 份重复 | airxdb, debug, repair | 同上 | +| P1-5 | merge-into-state 3 份重复 | airxdb, debug, repair | 同上 | +| P1-6 | marker block upsert 2 份(接口不同) | doc_sync, project_bootstrap | 同上 | +| P1-7 | `_session_stamp` 格式不一致 | airxdb, debug vs engine | 时间戳格式在不同模块产生不同文件名 | +| P1-8 | todo.md 列索引硬编码 | `doc_sync.py:154-156` | `cells[1]`=status, `cells[6]`=validation, `cells[7]`=adr — 表头变化时全部失效 | +| P1-9 | 并发度上限硬编码为 3 | `engine.py:527` | 无法根据项目规模调整 | +| P1-10 | 子进程无超时 | airxdb, debug runtime | 挂死时阻塞整个引擎 | +| P1-11 | task_id 路径注入 | `worker.py:59` | 无 `../` 遍历校验 | +| P1-12 | 标记注入风险 | `doc_sync.py` `_replace_marker_block()` | marker 字段含 `-->` 时可注入内容 | +| P1-13 | 静默吞异常 | session 文件损坏时 `except` 后 `continue` | 损坏文件不可见,无日志 | +| P1-14 | Arc 重规划后 Eng 无法衔接 | `engine.py` 计划解析 + `todo.md` 同步 | 中途变更需求后 Arc 重新生成规划,Eng 需多轮 AI 迭代才能恢复调度 | 调度引擎基于静态 todo.md 表格,无法增量吸收 Arc 的动态重规划结果 | +| P1-15 | 同文件无冲突任务被迫串行 | `review.py` 写集冲突检测 | 同文件不同区域(如 Qt 样式 vs 状态机)被判为冲突,被迫串行执行 | 冲突检测粒度为文件级而非区域级,无 worktree 隔离并行能力 | +| P1-16 | AirArc 跳过需求探讨直接生成规划 | AirArc SKILL.md / 命令文件 | 用户刚说一两句就自顾自生成计划并要求执行,未与用户充分探讨需求和分析架构 | SKILL.md 未强制"先探讨后规划"流程,缺少用户确认架构的门控 | +| P1-17 | AirDbg 未取证就盲改代码 | AirDbg SKILL.md / `debug_runtime.py` | 调试器不进行任何取证(抓包/截图/代码分析)就猜测原因并修改代码,引入新问题且污染代码库 | 7 步工作流为建议性不强制,无"先读后写"硬性门控——未执行任何取证行为就不允许修改代码 | +| P1-18 | 项目缺乏标准化日志体系 | 项目引导 / AirArc 规划 | 生成的代码无统一日志输出,debug/release 无法切换,问题排查困难 | 无项目级日志标准要求,AirArc 规划时未强制 spdlog 集成,AirRvr 审查时未检查日志完备性 | + +#### P2 — 限制规模化 + +| ID | 缺陷 | 位置 | 影响 | +|----|------|------|------| +| P2-1 | 冲突检测 O(n²) | `review.py` `combinations(active_tasks, 2)` | 100 任务时 ~495,000 次路径比较 | +| P2-2 | state.json 无界增长 | `engine.py` | `mergedResults` 等列表永不截断 | +| P2-3 | todo.md 每次操作全量重解析 | engine 多处调用 `parse_tasks()` | 大 todo 表时性能退化 | +| P2-4 | 零测试覆盖 | 整个 `air_runtime/` | 任何重构都有回归风险 | + +#### P3 — 限制用户体验 + +| ID | 缺陷 | 位置 | 影响 | +|----|------|------|------| +| P3-1 | AGENTS.md 膨胀 | AirEng sync 追加无去重 | 同一任务记录重复 2-3 次 | +| P3-2 | 写集刚性导致级联任务链 | 写集边界设计 | T-028 衍生 fix-001~005 + T-028b + T-028c | +| P3-3 | 并行 Worker 抢占共享硬件 | 无硬件资源感知 | kmsgrab 锁死、负载 7.59 自发重启 | +| P3-4 | 环境特定修复不可持久化 | 部署自动化不完整 | MonitorServiceD、cgroup v1 每次重启需手动修复 | +| P3-5 | 跨项目知识不迁移 | 无模板继承机制 | 每个项目从零积累运维经验 | + +### 1.2 插件级差距 + +| 插件 | V1 差距 | 影响 | +|------|---------|------| +| **AirContext** | 压缩质量无监控;Token 估算 `char_div_3.5` 粗糙;续传 prompt 硬编码中文;锁文件无陈旧检测 | 坏摘要静默损坏上下文 | +| **AirDbg** | 7 步工作流纯建议性不强制;无不可复现 bug 分支;无回滚能力 | 调试质量依赖模型自觉 | +| **AirXDB** | 无 headless CI;无 DRM/KMS 原生截图;无截图 diff;远程探测不含 ffmpeg | 生产渲染路径无法自动验证 | +| **AirNDB** | 无 TLS 解密;大 pcap `tail(8000)` 截断;无 pcapng 支持 | 大规模抓包分析能力不足 | +| **AirSDB** | 仅 C/C++;无 diff 模式;无 compile_commands.json 生成 | 多语言项目零覆盖 | +| **AirArc** | 无规划质量验证;无增量重规划;无执行→规划反馈 | scope 变更必须全量重新生成 | +| **AirEng** | 无级联故障保护;无资源耗尽监控;5 分钟固定轮询;无 Worker 总时间上限 | 大规模调度时稳定性不足 | + +### 1.3 真实项目痛点汇总 + +| 痛点 | 频次 | 根因缺陷 | +|------|------|---------| +| AirXDB 假阳性阻塞 | 11+ 任务 | P0-1 | +| 完成但未部署 | 1 次关键事故 | P0-2 | +| 写集级联任务链 | 5+ 条链 | P3-2 | +| 并行 Worker 抢占硬件 | 3+ 次 | P3-3 | +| 空壳修复循环 | 11+ 次 | P0-1 + P3-4 | +| AGENTS.md 膨胀 | 持续累积 | P3-1 | +| 环境修复不可持久 | 每次重启 | P3-4 | +| AirArc 被 plan 模式劫持 | 频繁 | P0-5 | +| AirEng 反复询问不自主推进 | 每次调度 | P0-6 | +| AirEng 遗忘轮询导致无限等待 | 频繁 | P0-7 | +| AirDo 跳过 AirDbg 直接返回 | 频繁 | P0-8 | +| 安装后插件无法识别或脚本路径错误 | 用户普遍反馈 | P0-9 | +| 需求变更后调度需多轮迭代恢复 | 每次变更 | P1-14 | +| 同文件无冲突任务被迫串行 | 频繁 | P1-15 | +| 实现偏离设计无对照机制 | 持续累积 | AirRvr 设计缺口 | +| AirArc 跳过需求探讨直接生成规划 | 每次启动 | P1-16 | +| AirDbg 不取证就猜测修复污染代码 | 频繁 | P1-17 | +| AirEng 偏离调度亲自写代码 | 频繁 | P0-10 | +| 项目代码缺乏标准化日志体系 | 所有项目 | P1-18 | + +--- + +## 2. V2 设计目标 + +### 2.1 核心目标 + +1. **可靠性**:状态写入不丢失,并发操作不竞态,崩溃后可自愈 +2. **可观测性**:所有引擎操作可追溯,指标可导出,异常主动通知 +3. **智能化**:证据门控感知任务类型,轮询频率自适应,修复模式可学习 +4. **规模化**:支持 100+ 任务、5+ 并行 Worker、多项目知识迁移 + +### 2.2 不变量 + +V2 必须保持 V1 的核心不变量: + +| 不变量 | V1 定义 | V2 保持方式 | +|--------|---------|------------| +| INV-1 制品驱动通信 | 插件间通过 AirPlan/ 文件通信 | 保持,增加事件索引层 | +| INV-2 上下文隔离 | Worker `fork_context=false` | 保持,增加选择性上下文继承 | +| INV-3 架构同步强制 | 不更新架构文档不能 DONE | 保持,增加增量同步 | +| INV-4 证据先于修复 | 截图/抓包/静态分析前置 | 保持,增加任务类型感知 | +| INV-5 闭环自动修复 | 执行→失败→调试→修复→重执行 | 保持,增加修复模式学习 | + +--- + +## 3. V2 架构改进 + +### 3.1 基础设施层重构 + +#### 3.1.1 `air_runtime.io` — 统一 I/O 模块 + +消除 5 份 `_json_dump`/`_json_load` 重复,统一为原子写入: + +```python +# air_runtime/io.py + +def atomic_json_write(path: Path, data: dict) -> None: + """POSIX 原子写入:tempfile + os.replace()""" + path.parent.mkdir(parents=True, exist_ok=True) + fd, tmp = tempfile.mkstemp(dir=path.parent, suffix=".tmp") + try: + os.write(fd, json.dumps(data, indent=2, ensure_ascii=False).encode("utf-8")) + os.close(fd) + os.replace(tmp, path) + except BaseException: + with contextlib.suppress(OSError): + os.unlink(tmp) + raise + +def safe_json_load(path: Path) -> dict | None: + """安全加载:处理损坏文件,自动从 .bak 恢复""" + try: + return json.loads(path.read_text("utf-8")) + except (json.JSONDecodeError, UnicodeDecodeError): + bak = path.with_suffix(path.suffix + ".bak") + if bak.exists(): + logging.warning("corrupt %s, restoring from %s", path, bak) + return json.loads(bak.read_text("utf-8")) + logging.error("corrupt %s with no backup", path) + return None +``` + +每次写入前自动备份旧文件为 `.bak`(单级轮转),保证至少有一次完整的历史版本。 + +#### 3.1.2 `air_runtime.lock` — 文件级并发控制 + +```python +# air_runtime/lock.py + +class FileLock: + """基于 fcntl.flock 的进程级文件锁""" + + def __init__(self, path: Path, timeout: float = 10.0): + self._path = path.with_suffix(path.suffix + ".lock") + self._timeout = timeout + self._fd = None + + def __enter__(self): + self._fd = os.open(self._path, os.O_CREAT | os.O_RDWR) + deadline = time.monotonic() + self._timeout + while True: + try: + fcntl.flock(self._fd, fcntl.LOCK_EX | fcntl.LOCK_NB) + return self + except OSError: + if time.monotonic() >= deadline: + raise TimeoutError(f"lock timeout: {self._path}") + time.sleep(0.1) + + def __exit__(self, *exc): + fcntl.flock(self._fd, fcntl.LOCK_UN) + os.close(self._fd) +``` + +所有 `state.json` 和 `todo.md` 的读-改-写操作必须持有对应锁。 + +#### 3.1.3 `air_runtime.utils` — 消除代码重复 + +```python +# air_runtime/utils.py + +def ordered_unique(items: list) -> list: + """保序去重""" + seen = set() + result = [] + for item in items: + key = item if isinstance(item, str) else item.get("id", str(item)) + if key not in seen: + seen.add(key) + result.append(item) + return result + +def session_stamp() -> str: + """统一的文件系统安全时间戳""" + return datetime.now(timezone.utc).isoformat().replace(":", "-").replace(".", "-").replace("+", "-") + +def normalize_policy(defaults: dict, overrides: dict | None) -> dict: + """通用的策略合并""" + merged = {**defaults} + if overrides: + for k, v in overrides.items(): + if k in merged: + expected_type = type(defaults[k]) + merged[k] = expected_type(v) if not isinstance(v, expected_type) else v + return merged + +def sanitize_task_id(task_id: str) -> str: + """防止路径注入""" + if not re.fullmatch(r"[A-Za-z0-9_\-]+", task_id): + raise ValueError(f"invalid task_id: {task_id!r}") + return task_id + +def sanitize_marker(marker: str) -> str: + """防止 HTML 注释注入""" + if "-->" in marker or "