feat: 3.2.17中途变更处理(git驱动) + 3.2.9a Worker git强制 + 3.2.5 Phase7 squash merge + Arc git初始化检测
- 新增 ChangeClassifier(爆炸半径分类:IMPLEMENTATION/INTERFACE/GLOBAL_CONSTRAINT) - 新增 ImpactPropagator(BFS影响传播:IMPACTED/BOUNDARY/SAFE差异化标记) - task_graph.py:invalidate_by_adr()差异化失效 + CascadeReport扩展字段(向后兼容) - eng_mode.py:三阶段差异化流程(分类→传播→失效→git操作→验证任务→重规划) - eng_mode.py:_git_squash_merge_and_tag() + Phase 7 集成 - do_mode.py:_ensure_all_committed() Worker git操作强制 - adr_watcher.py:内容快照 + get_content_for_classification() - events.py:ADR_CLASSIFIED/IMPACT_PROPAGATED/BOUNDARY_VERIFICATION_GENERATED - partial_replanner.py:replan_with_constraints() + generate_verification_tasks() - project_bootstrap.py:ensure_git_initialized() Arc规划前检测 - test_p1_21_phase2.py:21个新测试,95个全量测试0失败 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -40,12 +40,14 @@ class EdgeChange:
|
||||
|
||||
@dataclass
|
||||
class CascadeReport:
|
||||
"""P1-21: ADR 变更级联失效报告。"""
|
||||
"""P1-21: ADR 变更级联失效报告。3.2.17 差异化处理后新增 boundary/impact 字段。"""
|
||||
invalidated_completed: int = 0
|
||||
terminated_in_progress: int = 0
|
||||
cascaded_downstream: int = 0
|
||||
rollback_ref: str = ""
|
||||
invalidated_task_ids: list[str] = field(default_factory=list)
|
||||
boundary_task_ids: list[str] = field(default_factory=list)
|
||||
blast_radius: str = "" # "implementation" | "interface" | "global_constraint"
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -266,32 +268,40 @@ class TaskGraph:
|
||||
# 排除起点自身
|
||||
return [tid for tid in visited if tid not in set(task_ids)]
|
||||
|
||||
def invalidate_by_adr(self, adr_id: str, delta: PlanDelta) -> CascadeReport:
|
||||
"""P1-21: ADR 变更时级联失效所有相关任务。"""
|
||||
def invalidate_by_adr(self, adr_id: str, delta: PlanDelta,
|
||||
impact_labels: dict[str, str] | None = None) -> CascadeReport:
|
||||
"""P1-21: ADR 变更时级联失效相关任务。
|
||||
|
||||
当 impact_labels 为 None 时使用旧行为(所有引用+下游统一 INVALIDATED)。
|
||||
当 impact_labels 提供时使用差异化行为(IMPACTED→INVALIDATED, BOUNDARY+DONE→验证, SAFE→保留)。
|
||||
"""
|
||||
affected = self.tasks_by_adr(adr_id)
|
||||
completed = [t for t in affected if t.status == "DONE"]
|
||||
in_progress = [t for t in affected if t.status == "DISPATCHED"]
|
||||
pending = [t for t in affected if t.status == "TODO"]
|
||||
|
||||
# 1. 冻结调度
|
||||
self.dispatch_frozen = True
|
||||
|
||||
# 2. 标记已完成任务为 INVALIDATED
|
||||
if impact_labels is None:
|
||||
return self._invalidate_uniform(adr_id, delta, affected)
|
||||
|
||||
return self._invalidate_differentiated(adr_id, delta, affected, impact_labels)
|
||||
|
||||
def _invalidate_uniform(self, adr_id: str, delta: PlanDelta,
|
||||
affected: list[TaskNode]) -> CascadeReport:
|
||||
"""旧行为:所有引用 ADR 的任务及其下游统一 INVALIDATED。"""
|
||||
completed = [t for t in affected if t.status == "DONE"]
|
||||
in_progress = [t for t in affected if t.status == "DISPATCHED"]
|
||||
pending = [t for t in affected if t.status == "TODO"]
|
||||
|
||||
for t in completed:
|
||||
t.status = "INVALIDATED"
|
||||
delta.removed_tasks.append(t.id)
|
||||
|
||||
# 3. 标记进行中任务为 INVALIDATED(调用方负责中止 Worker)
|
||||
for t in in_progress:
|
||||
t.status = "INVALIDATED"
|
||||
delta.removed_tasks.append(t.id)
|
||||
|
||||
# 4. 标记 ADR 直接关联的 TODO 任务为 INVALIDATED
|
||||
for t in pending:
|
||||
t.status = "INVALIDATED"
|
||||
delta.removed_tasks.append(t.id)
|
||||
|
||||
# 5. 级联失效下游
|
||||
downstream_ids = self._find_downstream([t.id for t in completed + in_progress + pending])
|
||||
cascaded = []
|
||||
for tid in downstream_ids:
|
||||
@@ -301,7 +311,6 @@ class TaskGraph:
|
||||
delta.removed_tasks.append(tid)
|
||||
cascaded.append(tid)
|
||||
|
||||
# 6. 回滚快照引用(由调用方在 git revert 后填入)
|
||||
all_invalidated = [t.id for t in completed + in_progress + pending] + cascaded
|
||||
|
||||
return CascadeReport(
|
||||
@@ -312,6 +321,92 @@ class TaskGraph:
|
||||
invalidated_task_ids=all_invalidated,
|
||||
)
|
||||
|
||||
def _invalidate_differentiated(self, adr_id: str, delta: PlanDelta,
|
||||
affected: list[TaskNode],
|
||||
impact_labels: dict[str, str]) -> CascadeReport:
|
||||
"""差异化行为:按 IMPACTED/BOUNDARY/SAFE 分别处理每个任务。"""
|
||||
invalidated_completed = 0
|
||||
terminated_in_progress = 0
|
||||
invalidated_ids: list[str] = []
|
||||
boundary_ids: list[str] = []
|
||||
|
||||
from air_runtime.impact_propagator import ImpactLabel
|
||||
|
||||
for t in affected:
|
||||
label = impact_labels.get(t.id, ImpactLabel.SAFE.value)
|
||||
if label == ImpactLabel.SAFE.value:
|
||||
continue
|
||||
|
||||
if label == ImpactLabel.BOUNDARY.value:
|
||||
if t.status == "DONE":
|
||||
t.meta["verification_needed"] = True
|
||||
boundary_ids.append(t.id)
|
||||
elif t.status in ("TODO", "BLOCKED"):
|
||||
t.status = "INVALIDATED"
|
||||
delta.removed_tasks.append(t.id)
|
||||
invalidated_ids.append(t.id)
|
||||
# DISPATCHED + BOUNDARY: wait for completion, no action
|
||||
|
||||
elif label == ImpactLabel.IMPACTED.value:
|
||||
if t.status == "DONE":
|
||||
t.status = "INVALIDATED"
|
||||
delta.removed_tasks.append(t.id)
|
||||
invalidated_ids.append(t.id)
|
||||
invalidated_completed += 1
|
||||
elif t.status == "DISPATCHED":
|
||||
t.status = "INVALIDATED"
|
||||
delta.removed_tasks.append(t.id)
|
||||
invalidated_ids.append(t.id)
|
||||
terminated_in_progress += 1
|
||||
elif t.status in ("TODO", "BLOCKED"):
|
||||
t.status = "INVALIDATED"
|
||||
delta.removed_tasks.append(t.id)
|
||||
invalidated_ids.append(t.id)
|
||||
|
||||
# 级联下游:仅 IMPACTED 任务的下游受影响
|
||||
impacted_affected = [t for t in affected
|
||||
if impact_labels.get(t.id) == ImpactLabel.IMPACTED.value]
|
||||
cascaded_ids: list[str] = []
|
||||
if impacted_affected:
|
||||
downstream_ids = self._find_downstream([t.id for t in impacted_affected])
|
||||
for tid in downstream_ids:
|
||||
node = self.nodes.get(tid)
|
||||
if not node:
|
||||
continue
|
||||
dlabel = impact_labels.get(tid, ImpactLabel.SAFE.value)
|
||||
if dlabel == ImpactLabel.SAFE.value:
|
||||
continue
|
||||
if dlabel == ImpactLabel.BOUNDARY.value:
|
||||
if node.status == "DONE":
|
||||
node.meta["verification_needed"] = True
|
||||
boundary_ids.append(tid)
|
||||
elif node.status in ("TODO", "BLOCKED"):
|
||||
node.status = "INVALIDATED"
|
||||
delta.removed_tasks.append(tid)
|
||||
cascaded_ids.append(tid)
|
||||
invalidated_ids.append(tid)
|
||||
elif dlabel == ImpactLabel.IMPACTED.value:
|
||||
if node.status in ("TODO", "DISPATCHED"):
|
||||
node.status = "INVALIDATED"
|
||||
delta.removed_tasks.append(tid)
|
||||
cascaded_ids.append(tid)
|
||||
invalidated_ids.append(tid)
|
||||
elif node.status == "DONE":
|
||||
node.status = "INVALIDATED"
|
||||
delta.removed_tasks.append(tid)
|
||||
cascaded_ids.append(tid)
|
||||
invalidated_ids.append(tid)
|
||||
invalidated_completed += 1
|
||||
|
||||
return CascadeReport(
|
||||
invalidated_completed=invalidated_completed,
|
||||
terminated_in_progress=terminated_in_progress,
|
||||
cascaded_downstream=len(cascaded_ids),
|
||||
rollback_ref=delta.rollback_ref,
|
||||
invalidated_task_ids=invalidated_ids,
|
||||
boundary_task_ids=boundary_ids,
|
||||
)
|
||||
|
||||
def unfreeze_dispatch(self) -> None:
|
||||
"""P1-21: 解冻调度,在 Arc 重新生成受影响任务后调用。"""
|
||||
self.dispatch_frozen = False
|
||||
|
||||
Reference in New Issue
Block a user