新增: Reviewer仲裁+命令互斥锁+冲突解决IPC(批次C散项)

- coordinator: arbitrate_conflicts 方法(规则仲裁:成功方优先/双方成功推荐合并/都失败手动)

- coordinator: ConflictItem 加 subtask_a/subtask_b/recommendation 字段 + ConflictResolution 枚举

- command_lock.rs: CommandLockRegistry (dir+cmd首词 Semaphore 串行化,5个测试全绿)

- resolve_conflict IPC: 解决冲突→更新DB→emit AiConflictResolved 事件闭环

- 3个仲裁测试 + 5个互斥锁测试全绿

- 零编译警告
This commit is contained in:
2026-07-02 00:17:00 +08:00
parent 840949ad05
commit 61d3e9c148
5 changed files with 302 additions and 0 deletions

View File

@@ -123,6 +123,35 @@ pub struct ConflictItem {
pub file: String,
/// 冲突描述
pub description: String,
/// 冲突双方 SubTask id
pub subtask_a: Option<String>,
pub subtask_b: Option<String>,
/// Reviewer 仲裁推荐(Phase 2 LLM 仲裁填充)
pub recommendation: Option<ConflictResolution>,
}
/// 冲突解决方案
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConflictResolution {
/// 接受 A 的改动
AcceptA,
/// 接受 B 的改动
AcceptB,
/// 合并两者
Merged,
/// 用户手动处理
Manual,
}
impl ConflictResolution {
pub fn as_str(&self) -> &'static str {
match self {
Self::AcceptA => "a",
Self::AcceptB => "b",
Self::Merged => "merged",
Self::Manual => "manual",
}
}
}
// ---- Coordinator 实现 --------------------------------------------------------
@@ -445,11 +474,49 @@ impl Coordinator {
subtasks.len(),
subtasks.join(", ")
),
subtask_a: subtasks.first().cloned(),
subtask_b: subtasks.get(1).cloned(),
recommendation: None,
});
}
}
conflicts
}
/// 仲裁冲突:对每个冲突给出推荐解决方案
///
/// Phase 1:规则驱动(简单启发式)
/// - 只有一方成功的冲突 → 推荐成功方
/// - 两方都成功 → 推荐 Merged(需人工确认)
/// - 无法判断 → Manual(留给用户)
///
/// Phase 2 预留:LLM 驱动(读 diff → 推荐方案 + 理由)
pub fn arbitrate_conflicts(
&self,
conflicts: &mut [ConflictItem],
results: &[ExecutionResult],
) {
let success_ids: std::collections::HashSet<&str> =
results.iter().filter(|r| r.success).map(|r| r.subtask_id.as_str()).collect();
for c in conflicts.iter_mut() {
let a_ok = c.subtask_a.as_deref().map(|id| success_ids.contains(id)).unwrap_or(false);
let b_ok = c.subtask_b.as_deref().map(|id| success_ids.contains(id)).unwrap_or(false);
c.recommendation = Some(match (a_ok, b_ok) {
(true, false) => ConflictResolution::AcceptA,
(false, true) => ConflictResolution::AcceptB,
(true, true) => ConflictResolution::Merged,
(false, false) => ConflictResolution::Manual,
});
tracing::info!(
file = %c.file,
recommendation = ?c.recommendation,
"[REVIEWER] 冲突仲裁推荐"
);
}
}
}
/// 从执行产出文本中提取被写入的文件路径(简化检测:匹配 write_file/patch_file 后的路径)
@@ -785,6 +852,62 @@ mod tests {
assert!(files.is_empty());
}
// -- Reviewer 仲裁 --
#[test]
fn arb_01_both_success_recommends_merged() {
let coord = make_coord();
let mut conflicts = vec![ConflictItem {
file: "main.rs".into(),
description: "冲突".into(),
subtask_a: Some("A".into()),
subtask_b: Some("B".into()),
recommendation: None,
}];
let results = vec![
ExecutionResult { subtask_id: "A".into(), persona_id: "coder".into(), output: "".into(), success: true },
ExecutionResult { subtask_id: "B".into(), persona_id: "coder".into(), output: "".into(), success: true },
];
coord.arbitrate_conflicts(&mut conflicts, &results);
assert_eq!(conflicts[0].recommendation, Some(ConflictResolution::Merged));
}
#[test]
fn arb_02_a_failed_recommends_b() {
let coord = make_coord();
let mut conflicts = vec![ConflictItem {
file: "main.rs".into(),
description: "冲突".into(),
subtask_a: Some("A".into()),
subtask_b: Some("B".into()),
recommendation: None,
}];
let results = vec![
ExecutionResult { subtask_id: "A".into(), persona_id: "coder".into(), output: "".into(), success: false },
ExecutionResult { subtask_id: "B".into(), persona_id: "coder".into(), output: "".into(), success: true },
];
coord.arbitrate_conflicts(&mut conflicts, &results);
assert_eq!(conflicts[0].recommendation, Some(ConflictResolution::AcceptB));
}
#[test]
fn arb_03_both_failed_recommends_manual() {
let coord = make_coord();
let mut conflicts = vec![ConflictItem {
file: "main.rs".into(),
description: "冲突".into(),
subtask_a: Some("A".into()),
subtask_b: Some("B".into()),
recommendation: None,
}];
let results = vec![
ExecutionResult { subtask_id: "A".into(), persona_id: "coder".into(), output: "".into(), success: false },
ExecutionResult { subtask_id: "B".into(), persona_id: "coder".into(), output: "".into(), success: false },
];
coord.arbitrate_conflicts(&mut conflicts, &results);
assert_eq!(conflicts[0].recommendation, Some(ConflictResolution::Manual));
}
// -- DispatchStrategy --
#[test]