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:
AirPlan
2026-06-12 15:56:44 +08:00
commit 6130478c96
73 changed files with 10622 additions and 0 deletions

143
test_arc.py Executable file
View File

@@ -0,0 +1,143 @@
#!/usr/bin/env python3
"""Arc Mode 功能测试"""
import sys
import os
import tempfile
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent / "lib"))
def test_arc_phase_gate():
"""测试三阶段门控"""
from air_runtime.modes.arc_mode import ArcPhaseGate
with tempfile.TemporaryDirectory() as tmpdir:
state_path = Path(tmpdir) / "state.json"
gate = ArcPhaseGate(state_path)
# 初始阶段应该是 discussing
assert gate.current_phase == "discussing", f"初始阶段应为 discussing实际: {gate.current_phase}"
# 可以前进到 proposing
gate.advance_to("proposing")
assert gate.current_phase == "proposing"
# 不能跳到 confirmed只能逐步前进
gate.advance_to("confirmed")
assert gate.current_phase == "confirmed"
# can_write_plan 只在 confirmed 时返回 True
assert gate.can_write_plan() == True
# 测试确认架构
result = gate.confirm_architecture("好的,确认这个架构")
assert result == True
assert gate.current_phase == "confirmed"
print("✓ ArcPhaseGate 三阶段门控测试通过")
return True
def test_arc_build_graph():
"""测试 DAG 构建"""
from air_runtime.modes.arc_mode import _build_graph_from_todo
from air_runtime.task_graph import TaskGraph
with tempfile.TemporaryDirectory() as tmpdir:
todo_path = Path(tmpdir) / "todo.md"
todo_path.write_text("""
| Task | Status | Files/Dirs | Done When |
|------|--------|------------|-----------|
| [T-001] 任务1 | TODO | src/a/ | 完成 |
| [T-002] 任务2 | TODO | src/b/ | 完成 |
| [T-003] 任务3 | DONE | src/c/ | 完成 |
""")
graph, violations = _build_graph_from_todo(todo_path)
# 检查节点数量
assert len(graph.nodes) >= 3, f"应有至少3个节点实际: {len(graph.nodes)}"
# 检查是否有边界测试任务注入
test_nodes = [n for n in graph.nodes.values() if n.test_required]
assert len(test_nodes) > 0, "应该有边界测试任务"
# 检查 Done When 违规检测
assert len(violations) > 0, "应该有 Done When 不含'测试通过'的违规"
print(f"✓ DAG 构建测试通过,节点数: {len(graph.nodes)}, 测试任务: {len(test_nodes)}, 违规: {len(violations)}")
return True
def test_arc_parallel_review():
"""测试并行审查模式"""
from air_runtime.modes.arc_mode import parallel_review_mode
with tempfile.TemporaryDirectory() as tmpdir:
project_root = Path(tmpdir)
# 创建 AirPlan 目录结构
arc_root = project_root / "AirPlan" / "state" / "airarc"
arc_root.mkdir(parents=True, exist_ok=True)
# 写入 state.json需先有 confirmed 阶段)
import json
(arc_root / "state.json").write_text(json.dumps({"arcPhase": "confirmed"}))
# 创建 todo.md
todo_path = project_root / "AirPlan" / "todo.md"
todo_path.write_text("""
| Task | Status | Files/Dirs | Done When |
|------|--------|------------|-----------|
| [T-001] 任务1 | TODO | src/main.cpp | 编译通过 |
""")
result = parallel_review_mode(project_root, todo_path)
# 检查返回值
assert "execution_plan_json_path" in result
assert result.get("blocked") != True, "不应被阻止"
# 检查 execution-plan.json 是否生成
plan_path = result["execution_plan_json_path"]
assert Path(plan_path).exists(), f"执行计划文件应存在: {plan_path}"
print(f"✓ parallel_review_mode 测试通过")
return True
def main():
print("=" * 50)
print("Arc Mode 功能测试")
print("=" * 50)
tests = [
("三阶段门控", test_arc_phase_gate),
("DAG 构建", test_arc_build_graph),
("并行审查", test_arc_parallel_review),
]
passed = 0
failed = 0
for name, test_fn in tests:
try:
test_fn()
passed += 1
except Exception as e:
print(f"{name} 失败: {e}")
import traceback
traceback.print_exc()
failed += 1
print("=" * 50)
print(f"测试结果: {passed} 通过, {failed} 失败")
print("=" * 50)
return failed == 0
if __name__ == "__main__":
success = main()
sys.exit(0 if success else 1)