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>
This commit is contained in:
@@ -308,6 +308,32 @@ def dispatch_worker_group(project_root: Path, group_name: str = "") -> dict:
|
||||
return result
|
||||
|
||||
|
||||
def _detect_adr_changes(project_root: Path, state: dict) -> list:
|
||||
"""P1-21: 检查 ADR 文件变更,返回需要级联失效的变更列表。"""
|
||||
from air_runtime.adr_watcher import ADRWatcher, ADRChange
|
||||
adr_dir = project_root / "AirPlan" / "docs" / "architecture" / "adr"
|
||||
if not adr_dir.exists():
|
||||
return []
|
||||
|
||||
watcher = ADRWatcher(adr_dir)
|
||||
# 从引擎状态恢复已知 hash
|
||||
known = state.get("adrWatcherHashes", {})
|
||||
watcher._known_hashes = known
|
||||
|
||||
# 首次无 snapshot → 先初始化
|
||||
if not known:
|
||||
watcher.snapshot()
|
||||
state["adrWatcherHashes"] = dict(watcher._known_hashes)
|
||||
return []
|
||||
|
||||
changes = watcher.detect_changes()
|
||||
# 持久化更新后的 hash
|
||||
state["adrWatcherHashes"] = dict(watcher._known_hashes)
|
||||
|
||||
# 只返回需要级联失效的变更
|
||||
return [c for c in changes if c.kind in ("superseded", "modified")]
|
||||
|
||||
|
||||
def monitor_engine(project_root: Path) -> dict:
|
||||
"""L1 代码级轮询:硬编码循环检测 Worker 状态,不依赖 LLM 自觉。"""
|
||||
paths = _paths(project_root)
|
||||
@@ -350,6 +376,21 @@ def monitor_engine(project_root: Path) -> dict:
|
||||
except OSError:
|
||||
resource_pressure = False
|
||||
|
||||
# P1-21: ADR 变更自动检测
|
||||
adr_changes = _detect_adr_changes(project_root, state)
|
||||
if adr_changes:
|
||||
for change in adr_changes:
|
||||
if change.kind in ("superseded", "modified"):
|
||||
interventions.append({
|
||||
"adrId": change.adr_id,
|
||||
"reason": f"adr-{change.kind}",
|
||||
"action": "invalidate-by-adr",
|
||||
})
|
||||
log = EventLog(event_log_path(project_root))
|
||||
log.emit("adr.change.detected", {
|
||||
"adrId": change.adr_id, "kind": change.kind,
|
||||
})
|
||||
|
||||
# 新增:检查 pending worktree merges — merge 失败自动升级到 AirDbg
|
||||
wt_root = project_root / ".git" / "worktrees"
|
||||
if wt_root.exists():
|
||||
@@ -616,6 +657,13 @@ def handle_adr_invalidation(project_root: Path, adr_id: str) -> dict:
|
||||
# 6: git revert 已合并的旧代码(按 task_id 查找对应 commit)
|
||||
revert_results = _git_revert_invalidated(project_root, report.invalidated_task_ids)
|
||||
|
||||
# 6.5: 生成局部重规划请求(PartialReplanner)
|
||||
from air_runtime.partial_replanner import PartialReplanner
|
||||
replanner = PartialReplanner()
|
||||
partial_delta = replanner.replan(graph, report.invalidated_task_ids)
|
||||
replan_request_path = paths["plan_dir"] / f"replan-request-{session_stamp()}.json"
|
||||
atomic_json_write(replan_request_path, partial_delta.replan_request)
|
||||
|
||||
# 7: 写回更新后的 task-graph.json
|
||||
from air_runtime.modes.arc_mode import _export_task_graph_json
|
||||
_export_task_graph_json(graph, tg_json)
|
||||
@@ -641,6 +689,7 @@ def handle_adr_invalidation(project_root: Path, adr_id: str) -> dict:
|
||||
},
|
||||
"terminatedWorkers": terminated_workers,
|
||||
"revertResults": revert_results,
|
||||
"replanRequestPath": str(replan_request_path),
|
||||
"nextStep": "arc-replan-then-unfreeze",
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user