Files
AirPlan-V2/lib/air_runtime/partial_replanner.py
AirLongDian 90a0db005f feat: P1-21 补充 — ADRWatcher 自动检测 + PartialReplanner 局部重规划
- 新增 adr_watcher.py: SHA256 hash 监控 ADR 文件变更
  - snapshot() 初始快照, detect_changes() 增量检测
  - 识别 new/modified/superseded/deleted 四种变更类型
  - _parse_status() 解析 ADR Status 字段
- 新增 partial_replanner.py: 局部重规划器
  - replan() 仅生成受影响任务的替代计划
  - _extract_stable_interfaces() 提取未受影响 DONE 任务接口约束
  - 产出 replan-request.json 供 Arc 读取
- Eng monitor_engine 集成 ADR 检测: 每轮轮询调用 _detect_adr_changes()
- Eng handle_adr_invalidation 集成 PartialReplanner 输出
- events.py 新增 ADR 变更相关事件常量
- task_graph.py apply_full_replace 保留 DISPATCHED, 不保留 INVALIDATED
- 10 项补充测试全通过,含完整 ADR 流程场景

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 11:30:31 +08:00

107 lines
3.9 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 _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