Files
AirCoding ae44be31d5 chore: push all design docs, V2 plan specs, and current working state
Includes AirPlan design documents, AircOding-alpha1-plan, AirPlanV2,
AirPlan-ParaV2, AirPlan-Para V1 reference docs, and all working code
changes across packages.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-12 17:12:29 +08:00

126 lines
4.3 KiB
Python
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
并行审查模块 — V2 从 V1 迁移。
分析任务依赖、写集冲突、产出并行组和串行点。
"""
from __future__ import annotations
import re
from dataclasses import dataclass, field
from pathlib import Path
from air_runtime.todo_parser import parse_tasks
@dataclass
class ParallelGroup:
name: str
task_ids: list[str]
reason: str = ""
def to_dict(self) -> dict:
return {"name": self.name, "task_ids": self.task_ids, "reason": self.reason}
@dataclass
class Conflict:
task_a: str
task_b: str
reason: str = ""
def to_dict(self) -> dict:
return {"task_a": self.task_a, "task_b": self.task_b, "reason": self.reason}
@dataclass
class ReviewResult:
parallel_groups: list[ParallelGroup] = field(default_factory=list)
conflicts: list[Conflict] = field(default_factory=list)
serialization_points: list[dict] = field(default_factory=list)
edges: list[dict] = field(default_factory=list)
def to_dict(self) -> dict:
return {
"parallelGroups": [{"name": g.name, "task_ids": g.task_ids, "reason": g.reason} for g in self.parallel_groups],
"conflicts": [{"task_a": c.task_a, "task_b": c.task_b, "reason": c.reason} for c in self.conflicts],
"serializationPoints": self.serialization_points,
"edges": self.edges,
}
@classmethod
def from_dict(cls, data: dict) -> ReviewResult:
return cls(
parallel_groups=[ParallelGroup(**g) for g in data.get("parallelGroups", [])],
conflicts=[Conflict(**c) for c in data.get("conflicts", [])],
serialization_points=data.get("serializationPoints", []),
edges=data.get("edges", []),
)
def build_parallel_review(todo_path: Path) -> ReviewResult:
"""分析 todo.md产出并行组和冲突。"""
tasks = parse_tasks(todo_path)
result = ReviewResult()
# 解析依赖task 文本中的 "依赖 T-xxx" 或 Done When 中的引用
edges = []
for t in tasks:
deps = re.findall(r"T-\d+[a-z]*", t.done_when)
deps.extend(re.findall(r"依赖\s+(T-\d+[a-z]*)", t.task))
for dep in deps:
if dep != t.task_id:
edges.append({"source": dep, "target": t.task_id, "kind": "dependency"})
result.edges.append({"source": dep, "target": t.task_id, "kind": "dependency"})
# 写集冲突检测
file_map: dict[str, list[str]] = {}
for t in tasks:
if t.files_dirs:
files = [f.strip() for f in t.files_dirs.split(",")]
for f in files:
file_map.setdefault(f, []).append(t.task_id)
conflicts = []
for fpath, tid_list in file_map.items():
for i, tid_a in enumerate(tid_list):
for tid_b in tid_list[i + 1:]:
conflicts.append(Conflict(tid_a, tid_b, f"shared file: {fpath}"))
result.conflicts = conflicts
# 串行点:同文件不同任务的依赖链
for fpath, tid_list in file_map.items():
if len(tid_list) > 1:
for tid in tid_list[1:]:
result.serialization_points.append({
"taskId": tid,
"reasons": [f"serialized with {tid_list[0]} due to shared file: {fpath}"],
})
# 并行组:入度为 0 的任务
target_count = {e["target"] for e in edges}
ready = [t.task_id for t in tasks if t.task_id not in target_count and t.status == "TODO"]
if ready:
result.parallel_groups.append(ParallelGroup(
name="wave-1", task_ids=ready,
reason="no dependencies on other TODO tasks",
))
return result
def render_review_markdown(review: ReviewResult) -> str:
lines = ["# AirArc Parallel Review", ""]
lines.append(f"## Summary")
lines.append(f"- Parallel groups: {len(review.parallel_groups)}")
lines.append(f"- Conflicts: {len(review.conflicts)}")
lines.append(f"- Serialization points: {len(review.serialization_points)}")
lines.append("")
lines.append("## Parallel Groups")
for g in review.parallel_groups:
lines.append(f"### {g.name}")
lines.append(f"Reason: {g.reason}")
lines.append(f"Tasks: {', '.join(g.task_ids)}")
lines.append("")
lines.append("## Conflicts")
for c in review.conflicts:
lines.append(f"- {c.task_a} <-> {c.task_b}: {c.reason}")
return "\n".join(lines)