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>
This commit is contained in:
AirCoding
2026-06-12 17:12:29 +08:00
parent 8f55c962bb
commit ae44be31d5
364 changed files with 46779 additions and 2812 deletions

View File

@@ -0,0 +1,157 @@
"""
Worktree 隔离并行模块 — V2 P1-15 修复。
区域级冲突检测 + git worktree 隔离,允许同文件不同区域任务安全并行。
"""
from __future__ import annotations
import enum
import subprocess
from dataclasses import dataclass
from pathlib import Path
class ConflictLevel(enum.Enum):
NONE = "none" # 无文件重叠,直接并行
SOFT = "soft" # 同文件不同区域worktree 隔离并行
HARD = "hard" # 同文件同区域,必须串行
@dataclass
class Conflict:
task_a: str
task_b: str
level: ConflictLevel # NONE / SOFT / HARD
reason: str = ""
def to_dict(self) -> dict:
return {
"taskA": self.task_a,
"taskB": self.task_b,
"level": self.level.value,
"reason": self.reason,
}
@dataclass
class MergeResult:
task_id: str
successful: bool
conflicts: list[str]
class RegionConflictDetector:
"""区域级写集冲突检测。
区域定义(按优先级):
1. todo.md 中的区域标注 [region: xxx]
2. 函数/类边界AST 分析或标记注释)
3. 行号区间(从 diff 或任务元数据获取)
"""
def detect(self, task_a_write_set: list[str], task_b_write_set: list[str],
regions_a: dict[str, list[tuple[int, int]]] | None = None,
regions_b: dict[str, list[tuple[int, int]]] | None = None) -> ConflictLevel:
file_overlap = set(task_a_write_set) & set(task_b_write_set)
if not file_overlap:
return ConflictLevel.NONE
if regions_a is None or regions_b is None:
return ConflictLevel.SOFT
for fpath in file_overlap:
ra = regions_a.get(fpath, [(0, 999999)])
rb = regions_b.get(fpath, [(0, 999999)])
for a_start, a_end in ra:
for b_start, b_end in rb:
if a_start < b_end and b_start < a_end:
return ConflictLevel.HARD
return ConflictLevel.SOFT
def detect_batch(self, task_write_sets: dict[str, list[str]],
task_regions: dict[str, dict[str, list[tuple[int, int]]]] | None = None,
) -> list[Conflict]:
"""批量检测:接收 {task_id: [file, ...]} 映射,返回所有冲突对。
逐对调用 detect(),仅返回 level != NONE 的冲突。
"""
conflicts: list[Conflict] = []
task_ids = list(task_write_sets.keys())
for i in range(len(task_ids)):
for j in range(i + 1, len(task_ids)):
a, b = task_ids[i], task_ids[j]
ra = task_regions.get(a) if task_regions else None
rb = task_regions.get(b) if task_regions else None
level = self.detect(task_write_sets[a], task_write_sets[b], ra, rb)
if level != ConflictLevel.NONE:
overlap = sorted(set(task_write_sets[a]) & set(task_write_sets[b]))
reason = f"file overlap: {', '.join(overlap)}" if overlap else ""
if level == ConflictLevel.HARD:
reason = f"region overlap: {', '.join(overlap)}" if overlap else "same region"
conflicts.append(Conflict(task_a=a, task_b=b, level=level, reason=reason))
return conflicts
def extract_regions_from_task(self, file_path: Path, region_markers: list[tuple[int, int]]) -> dict[str, list[tuple[int, int]]]:
"""从任务的区域标注提取行号区间。"""
return {str(file_path): region_markers}
class WorktreeIsolation:
"""为 SOFT 冲突任务创建 git worktree 隔离,完成后合并回主分支。"""
def __init__(self, repo_root: Path):
self._repo_root = repo_root
def create_worktree(self, task_id: str, base_ref: str = "HEAD") -> Path:
branch = f"air-{task_id}"
wt_path = self._repo_root.parent / f"{self._repo_root.name}-air-{task_id}"
subprocess.run(
["git", "-C", str(self._repo_root), "worktree", "add", "-b", branch, str(wt_path), base_ref],
check=True, capture_output=True, text=True,
)
return wt_path
def merge_back(self, task_id: str, wt_path: Path) -> MergeResult:
branch = f"air-{task_id}"
try:
subprocess.run(
["git", "-C", str(self._repo_root), "merge", "--no-ff", branch],
check=True, capture_output=True, text=True,
)
return MergeResult(task_id=task_id, successful=True, conflicts=[])
except subprocess.CalledProcessError as exc:
conflicts = self._parse_conflicts(exc.stderr)
self._abort_merge()
return MergeResult(task_id=task_id, successful=False, conflicts=conflicts)
def cleanup(self, task_id: str, wt_path: Path) -> None:
branch = f"air-{task_id}"
try:
subprocess.run(
["git", "-C", str(self._repo_root), "worktree", "remove", str(wt_path), "--force"],
check=True, capture_output=True, text=True,
)
subprocess.run(
["git", "-C", str(self._repo_root), "branch", "-D", branch],
check=True, capture_output=True, text=True,
)
except subprocess.CalledProcessError:
pass
def _abort_merge(self) -> None:
try:
subprocess.run(
["git", "-C", str(self._repo_root), "merge", "--abort"],
check=True, capture_output=True, text=True,
)
except subprocess.CalledProcessError:
pass
@staticmethod
def _parse_conflicts(stderr: str) -> list[str]:
conflicts: list[str] = []
for line in stderr.splitlines():
if "CONFLICT" in line:
conflicts.append(line.strip())
return conflicts