新增: 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:
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user