- 新增 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>
163 lines
6.3 KiB
Python
163 lines
6.3 KiB
Python
"""
|
||
局部重规划 — P1-21 仅重新生成受 ADR 变更影响的任务子集。
|
||
替代全量重规划,保留未受影响任务的接口约束。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from dataclasses import dataclass, field
|
||
from pathlib import Path
|
||
from typing import Any
|
||
|
||
from air_runtime.task_graph import TaskGraph, TaskNode, PlanDelta
|
||
|
||
|
||
@dataclass
|
||
class Interface:
|
||
"""未受影响任务暴露的公共接口约束。"""
|
||
task_id: str
|
||
write_set: list[str] = field(default_factory=list)
|
||
adr_refs: list[str] = field(default_factory=list)
|
||
|
||
|
||
@dataclass
|
||
class ReplanContext:
|
||
"""受影响任务的上下文信息,供 Arc 局部重规划使用。"""
|
||
task_id: str
|
||
task: str
|
||
files_dirs: str
|
||
done_when: str
|
||
write_set: list[str] = field(default_factory=list)
|
||
adr_refs: list[str] = field(default_factory=list)
|
||
status: str = ""
|
||
|
||
|
||
class PartialReplanner:
|
||
"""仅重新生成受 ADR 变更影响的任务子集。
|
||
|
||
与 incremental_replan_mode 的区别:
|
||
- incremental_replan_mode: 全量重建 DAG 再 diff
|
||
- PartialReplanner: 只对受影响部分重新规划,保留稳定接口约束
|
||
"""
|
||
|
||
def replan(self, graph: TaskGraph, invalidated_ids: list[str],
|
||
new_adr_path: Path | None = None) -> PlanDelta:
|
||
"""局部重规划:仅生成受影响任务的替代任务。
|
||
|
||
Args:
|
||
graph: 当前任务图(已包含 INVALIDATED 标记)
|
||
invalidated_ids: 被 ADR 变更级联失效的任务 ID 列表
|
||
new_adr_path: 新 ADR 文件路径(可选,供 Arc 参考)
|
||
"""
|
||
delta = PlanDelta()
|
||
|
||
# 1. 收集受影响任务的上下文
|
||
affected_context = self._collect_affected_context(graph, invalidated_ids)
|
||
|
||
# 2. 提取未受影响任务的稳定接口
|
||
stable_interfaces = self._extract_stable_interfaces(graph, set(invalidated_ids))
|
||
|
||
# 3. 生成局部重规划指令文件(供 Arc 读取)
|
||
replan_request = {
|
||
"type": "partial-replan",
|
||
"invalidatedTaskIds": invalidated_ids,
|
||
"affectedContext": [ctx.__dict__ for ctx in affected_context],
|
||
"stableInterfaces": [iface.__dict__ for iface in stable_interfaces],
|
||
"newAdrPath": str(new_adr_path) if new_adr_path else None,
|
||
}
|
||
|
||
# 4. 构建增量 delta
|
||
# removed_tasks 已在 invalidate_by_adr 中填充
|
||
# added_tasks 留空——由 Arc 读取 replan-request.json 后生成新任务
|
||
delta.replan_request = replan_request
|
||
|
||
return delta
|
||
|
||
def replan_with_constraints(self, graph: TaskGraph,
|
||
invalidated_ids: list[str],
|
||
frozen_interfaces: list[Interface],
|
||
new_adr_path: Path | None = None) -> PlanDelta:
|
||
"""带冻结接口约束的局部重规划(3.2.17 Phase 3)。
|
||
|
||
frozen_interfaces 来自 SAFE 已完成任务,重规划不能破坏这些接口。
|
||
"""
|
||
delta = PlanDelta()
|
||
|
||
affected_context = self._collect_affected_context(graph, invalidated_ids)
|
||
stable_interfaces = self._extract_stable_interfaces(graph, set(invalidated_ids))
|
||
|
||
all_frozen = list(stable_interfaces)
|
||
for fi in frozen_interfaces:
|
||
if not any(s.task_id == fi.task_id for s in all_frozen):
|
||
all_frozen.append(fi)
|
||
|
||
replan_request = {
|
||
"type": "partial-replan",
|
||
"invalidatedTaskIds": invalidated_ids,
|
||
"affectedContext": [ctx.__dict__ for ctx in affected_context],
|
||
"stableInterfaces": [iface.__dict__ for iface in all_frozen],
|
||
"newAdrPath": str(new_adr_path) if new_adr_path else None,
|
||
}
|
||
delta.replan_request = replan_request
|
||
return delta
|
||
|
||
def generate_verification_tasks(self, graph: TaskGraph,
|
||
boundary_task_ids: list[str]) -> PlanDelta:
|
||
"""为 BOUNDARY 已完成任务生成验证任务(3.2.17 Phase 3.4)。
|
||
|
||
验证任务检查 BOUNDARY 任务的兼容性,确认是否与新 ADR 一致。
|
||
"""
|
||
from air_runtime.task_graph import TaskNode, Edge
|
||
delta = PlanDelta()
|
||
for idx, tid in enumerate(boundary_task_ids):
|
||
node = graph.nodes.get(tid)
|
||
if not node or node.status != "DONE":
|
||
continue
|
||
verify_id = f"{tid}-VFY"
|
||
verify_node = TaskNode(
|
||
id=verify_id,
|
||
status="TODO",
|
||
task=f"[边界验证] 验证 {tid} ({node.task}) 与新 ADR 的兼容性",
|
||
files_dirs=node.files_dirs,
|
||
done_when="兼容性确认通过",
|
||
write_set=list(node.write_set),
|
||
test_required=True,
|
||
)
|
||
delta.added_tasks.append(verify_node)
|
||
delta.edge_changes.added.append(Edge(
|
||
source=tid, target=verify_id, kind="verification",
|
||
))
|
||
return delta
|
||
|
||
def _collect_affected_context(self, graph: TaskGraph,
|
||
invalidated_ids: list[str]) -> list[ReplanContext]:
|
||
"""收集受影响任务的上下文。"""
|
||
contexts = []
|
||
for tid in invalidated_ids:
|
||
node = graph.nodes.get(tid)
|
||
if node:
|
||
contexts.append(ReplanContext(
|
||
task_id=node.id,
|
||
task=node.task,
|
||
files_dirs=node.files_dirs,
|
||
done_when=node.done_when,
|
||
write_set=list(node.write_set),
|
||
adr_refs=list(node.adr_refs),
|
||
status=node.status,
|
||
))
|
||
return contexts
|
||
|
||
def _extract_stable_interfaces(self, graph: TaskGraph,
|
||
invalidated_ids: set) -> list[Interface]:
|
||
"""提取未受影响 DONE 任务的接口约束,确保重规划不破坏依赖。"""
|
||
interfaces = []
|
||
for nid, node in graph.nodes.items():
|
||
if nid not in invalidated_ids and node.status == "DONE":
|
||
if node.write_set or node.adr_refs:
|
||
interfaces.append(Interface(
|
||
task_id=node.id,
|
||
write_set=list(node.write_set),
|
||
adr_refs=list(node.adr_refs),
|
||
))
|
||
return interfaces
|