mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-09 10:53:34 +08:00
feat: add ecc2 worktree conflict protocol
This commit is contained in:
286
ecc2/src/main.rs
286
ecc2/src/main.rs
@@ -203,6 +203,20 @@ enum Commands {
|
||||
#[arg(long)]
|
||||
check: bool,
|
||||
},
|
||||
/// Show conflict-resolution protocol for a worktree
|
||||
WorktreeResolution {
|
||||
/// Session ID or alias
|
||||
session_id: Option<String>,
|
||||
/// Show conflict protocol for all conflicted worktrees
|
||||
#[arg(long)]
|
||||
all: bool,
|
||||
/// Emit machine-readable JSON instead of the human summary
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
/// Return a non-zero exit code when conflicted worktrees are present
|
||||
#[arg(long)]
|
||||
check: bool,
|
||||
},
|
||||
/// Merge a session worktree branch into its base branch
|
||||
MergeWorktree {
|
||||
/// Session ID or alias
|
||||
@@ -704,6 +718,46 @@ async fn main() -> Result<()> {
|
||||
std::process::exit(worktree_status_reports_exit_code(&reports));
|
||||
}
|
||||
}
|
||||
Some(Commands::WorktreeResolution {
|
||||
session_id,
|
||||
all,
|
||||
json,
|
||||
check,
|
||||
}) => {
|
||||
if all && session_id.is_some() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"worktree-resolution does not accept a session ID when --all is set"
|
||||
));
|
||||
}
|
||||
let reports = if all {
|
||||
session::manager::list_sessions(&db)?
|
||||
.into_iter()
|
||||
.map(|session| build_worktree_resolution_report(&session))
|
||||
.collect::<Result<Vec<_>>>()?
|
||||
.into_iter()
|
||||
.filter(|report| report.conflicted)
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
let id = session_id.unwrap_or_else(|| "latest".to_string());
|
||||
let resolved_id = resolve_session_id(&db, &id)?;
|
||||
let session = db
|
||||
.get_session(&resolved_id)?
|
||||
.ok_or_else(|| anyhow::anyhow!("Session not found: {resolved_id}"))?;
|
||||
vec![build_worktree_resolution_report(&session)?]
|
||||
};
|
||||
if json {
|
||||
if all {
|
||||
println!("{}", serde_json::to_string_pretty(&reports)?);
|
||||
} else {
|
||||
println!("{}", serde_json::to_string_pretty(&reports[0])?);
|
||||
}
|
||||
} else {
|
||||
println!("{}", format_worktree_resolution_reports_human(&reports));
|
||||
}
|
||||
if check {
|
||||
std::process::exit(worktree_resolution_reports_exit_code(&reports));
|
||||
}
|
||||
}
|
||||
Some(Commands::MergeWorktree {
|
||||
session_id,
|
||||
all,
|
||||
@@ -987,6 +1041,22 @@ struct WorktreeStatusReport {
|
||||
merge_readiness: Option<WorktreeMergeReadinessReport>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
struct WorktreeResolutionReport {
|
||||
session_id: String,
|
||||
task: String,
|
||||
session_state: String,
|
||||
attached: bool,
|
||||
conflicted: bool,
|
||||
check_exit_code: i32,
|
||||
path: Option<String>,
|
||||
branch: Option<String>,
|
||||
base_branch: Option<String>,
|
||||
summary: String,
|
||||
conflicts: Vec<String>,
|
||||
resolution_steps: Vec<String>,
|
||||
}
|
||||
|
||||
fn build_worktree_status_report(session: &session::Session, include_patch: bool) -> Result<WorktreeStatusReport> {
|
||||
let Some(worktree) = session.worktree.as_ref() else {
|
||||
return Ok(WorktreeStatusReport {
|
||||
@@ -1047,6 +1117,55 @@ fn build_worktree_status_report(session: &session::Session, include_patch: bool)
|
||||
})
|
||||
}
|
||||
|
||||
fn build_worktree_resolution_report(session: &session::Session) -> Result<WorktreeResolutionReport> {
|
||||
let Some(worktree) = session.worktree.as_ref() else {
|
||||
return Ok(WorktreeResolutionReport {
|
||||
session_id: session.id.clone(),
|
||||
task: session.task.clone(),
|
||||
session_state: session.state.to_string(),
|
||||
attached: false,
|
||||
conflicted: false,
|
||||
check_exit_code: 0,
|
||||
path: None,
|
||||
branch: None,
|
||||
base_branch: None,
|
||||
summary: "No worktree attached".to_string(),
|
||||
conflicts: Vec::new(),
|
||||
resolution_steps: Vec::new(),
|
||||
});
|
||||
};
|
||||
|
||||
let merge_readiness = worktree::merge_readiness(worktree)?;
|
||||
let conflicted = merge_readiness.status == worktree::MergeReadinessStatus::Conflicted;
|
||||
let resolution_steps = if conflicted {
|
||||
vec![
|
||||
format!("Inspect current patch: ecc worktree-status {} --patch", session.id),
|
||||
format!("Open worktree: cd {}", worktree.path.display()),
|
||||
"Resolve conflicts and stage files: git add <paths>".to_string(),
|
||||
format!("Commit the resolution on {}: git commit", worktree.branch),
|
||||
format!("Re-check readiness: ecc worktree-status {} --check", session.id),
|
||||
format!("Merge when clear: ecc merge-worktree {}", session.id),
|
||||
]
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
Ok(WorktreeResolutionReport {
|
||||
session_id: session.id.clone(),
|
||||
task: session.task.clone(),
|
||||
session_state: session.state.to_string(),
|
||||
attached: true,
|
||||
conflicted,
|
||||
check_exit_code: if conflicted { 2 } else { 0 },
|
||||
path: Some(worktree.path.display().to_string()),
|
||||
branch: Some(worktree.branch.clone()),
|
||||
base_branch: Some(worktree.base_branch.clone()),
|
||||
summary: merge_readiness.summary,
|
||||
conflicts: merge_readiness.conflicts,
|
||||
resolution_steps,
|
||||
})
|
||||
}
|
||||
|
||||
fn format_worktree_status_human(report: &WorktreeStatusReport) -> String {
|
||||
let mut lines = vec![format!(
|
||||
"Worktree status for {} [{}]",
|
||||
@@ -1102,6 +1221,58 @@ fn format_worktree_status_reports_human(reports: &[WorktreeStatusReport]) -> Str
|
||||
.join("\n\n")
|
||||
}
|
||||
|
||||
fn format_worktree_resolution_human(report: &WorktreeResolutionReport) -> String {
|
||||
let mut lines = vec![format!(
|
||||
"Worktree resolution for {} [{}]",
|
||||
short_session(&report.session_id),
|
||||
report.session_state
|
||||
)];
|
||||
lines.push(format!("Task {}", report.task));
|
||||
|
||||
if !report.attached {
|
||||
lines.push(report.summary.clone());
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
if let Some(path) = report.path.as_ref() {
|
||||
lines.push(format!("Path {path}"));
|
||||
}
|
||||
if let (Some(branch), Some(base_branch)) = (report.branch.as_ref(), report.base_branch.as_ref()) {
|
||||
lines.push(format!("Branch {branch} (base {base_branch})"));
|
||||
}
|
||||
lines.push(report.summary.clone());
|
||||
|
||||
if !report.conflicts.is_empty() {
|
||||
lines.push("Conflicts".to_string());
|
||||
for conflict in &report.conflicts {
|
||||
lines.push(format!("- {conflict}"));
|
||||
}
|
||||
}
|
||||
|
||||
if report.resolution_steps.is_empty() {
|
||||
lines.push("No conflict-resolution steps required".to_string());
|
||||
} else {
|
||||
lines.push("Resolution steps".to_string());
|
||||
for (index, step) in report.resolution_steps.iter().enumerate() {
|
||||
lines.push(format!("{}. {step}", index + 1));
|
||||
}
|
||||
}
|
||||
|
||||
lines.join("\n")
|
||||
}
|
||||
|
||||
fn format_worktree_resolution_reports_human(reports: &[WorktreeResolutionReport]) -> String {
|
||||
if reports.is_empty() {
|
||||
return "No conflicted worktrees found".to_string();
|
||||
}
|
||||
|
||||
reports
|
||||
.iter()
|
||||
.map(format_worktree_resolution_human)
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n\n")
|
||||
}
|
||||
|
||||
fn format_worktree_merge_human(outcome: &session::manager::WorktreeMergeOutcome) -> String {
|
||||
let mut lines = vec![format!(
|
||||
"Merged worktree for {}",
|
||||
@@ -1192,6 +1363,14 @@ fn worktree_status_reports_exit_code(reports: &[WorktreeStatusReport]) -> i32 {
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
fn worktree_resolution_reports_exit_code(reports: &[WorktreeResolutionReport]) -> i32 {
|
||||
reports
|
||||
.iter()
|
||||
.map(|report| report.check_exit_code)
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
fn format_prune_worktrees_human(outcome: &session::manager::WorktreePruneOutcome) -> String {
|
||||
let mut lines = Vec::new();
|
||||
|
||||
@@ -1626,6 +1805,48 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_parses_worktree_resolution_flags() {
|
||||
let cli = Cli::try_parse_from(["ecc", "worktree-resolution", "planner", "--json", "--check"])
|
||||
.expect("worktree-resolution flags should parse");
|
||||
|
||||
match cli.command {
|
||||
Some(Commands::WorktreeResolution {
|
||||
session_id,
|
||||
all,
|
||||
json,
|
||||
check,
|
||||
}) => {
|
||||
assert_eq!(session_id.as_deref(), Some("planner"));
|
||||
assert!(!all);
|
||||
assert!(json);
|
||||
assert!(check);
|
||||
}
|
||||
_ => panic!("expected worktree-resolution subcommand"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_parses_worktree_resolution_all_flag() {
|
||||
let cli = Cli::try_parse_from(["ecc", "worktree-resolution", "--all"])
|
||||
.expect("worktree-resolution --all should parse");
|
||||
|
||||
match cli.command {
|
||||
Some(Commands::WorktreeResolution {
|
||||
session_id,
|
||||
all,
|
||||
json,
|
||||
check,
|
||||
}) => {
|
||||
assert!(session_id.is_none());
|
||||
assert!(all);
|
||||
assert!(!json);
|
||||
assert!(!check);
|
||||
}
|
||||
_ => panic!("expected worktree-resolution subcommand"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_parses_prune_worktrees_json_flag() {
|
||||
let cli = Cli::try_parse_from(["ecc", "prune-worktrees", "--json"])
|
||||
@@ -1721,6 +1942,71 @@ mod tests {
|
||||
assert!(text.contains("--- Branch diff vs main ---"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_worktree_resolution_human_includes_protocol_steps() {
|
||||
let report = WorktreeResolutionReport {
|
||||
session_id: "deadbeefcafefeed".to_string(),
|
||||
task: "Resolve merge conflict".to_string(),
|
||||
session_state: "stopped".to_string(),
|
||||
attached: true,
|
||||
conflicted: true,
|
||||
check_exit_code: 2,
|
||||
path: Some("/tmp/ecc/wt-1".to_string()),
|
||||
branch: Some("ecc/deadbeefcafefeed".to_string()),
|
||||
base_branch: Some("main".to_string()),
|
||||
summary: "Merge blocked by 1 conflict(s): README.md".to_string(),
|
||||
conflicts: vec!["README.md".to_string()],
|
||||
resolution_steps: vec![
|
||||
"Inspect current patch: ecc worktree-status deadbeefcafefeed --patch".to_string(),
|
||||
"Open worktree: cd /tmp/ecc/wt-1".to_string(),
|
||||
"Resolve conflicts and stage files: git add <paths>".to_string(),
|
||||
],
|
||||
};
|
||||
|
||||
let text = format_worktree_resolution_human(&report);
|
||||
assert!(text.contains("Worktree resolution for deadbeef [stopped]"));
|
||||
assert!(text.contains("Merge blocked by 1 conflict(s): README.md"));
|
||||
assert!(text.contains("Conflicts"));
|
||||
assert!(text.contains("- README.md"));
|
||||
assert!(text.contains("Resolution steps"));
|
||||
assert!(text.contains("1. Inspect current patch"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn worktree_resolution_reports_exit_code_tracks_conflicts() {
|
||||
let clear = WorktreeResolutionReport {
|
||||
session_id: "clear".to_string(),
|
||||
task: "ok".to_string(),
|
||||
session_state: "stopped".to_string(),
|
||||
attached: false,
|
||||
conflicted: false,
|
||||
check_exit_code: 0,
|
||||
path: None,
|
||||
branch: None,
|
||||
base_branch: None,
|
||||
summary: "No worktree attached".to_string(),
|
||||
conflicts: Vec::new(),
|
||||
resolution_steps: Vec::new(),
|
||||
};
|
||||
let conflicted = WorktreeResolutionReport {
|
||||
session_id: "conflicted".to_string(),
|
||||
task: "resolve".to_string(),
|
||||
session_state: "failed".to_string(),
|
||||
attached: true,
|
||||
conflicted: true,
|
||||
check_exit_code: 2,
|
||||
path: Some("/tmp/ecc/wt-2".to_string()),
|
||||
branch: Some("ecc/conflicted".to_string()),
|
||||
base_branch: Some("main".to_string()),
|
||||
summary: "Merge blocked by 1 conflict(s): src/lib.rs".to_string(),
|
||||
conflicts: vec!["src/lib.rs".to_string()],
|
||||
resolution_steps: vec!["Inspect current patch".to_string()],
|
||||
};
|
||||
|
||||
assert_eq!(worktree_resolution_reports_exit_code(&[clear]), 0);
|
||||
assert_eq!(worktree_resolution_reports_exit_code(&[conflicted]), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_prune_worktrees_human_reports_cleaned_and_active_sessions() {
|
||||
let text = format_prune_worktrees_human(&session::manager::WorktreePruneOutcome {
|
||||
|
||||
@@ -46,6 +46,7 @@ pub async fn run(db: StateStore, cfg: Config) -> Result<()> {
|
||||
(_, KeyCode::Char('g')) => dashboard.auto_dispatch_backlog().await,
|
||||
(_, KeyCode::Char('G')) => dashboard.coordinate_backlog().await,
|
||||
(_, KeyCode::Char('v')) => dashboard.toggle_output_mode(),
|
||||
(_, KeyCode::Char('c')) => dashboard.toggle_conflict_protocol_mode(),
|
||||
(_, KeyCode::Char('m')) => dashboard.merge_selected_worktree().await,
|
||||
(_, KeyCode::Char('M')) => dashboard.merge_ready_worktrees().await,
|
||||
(_, KeyCode::Char('p')) => dashboard.toggle_auto_dispatch_policy(),
|
||||
|
||||
@@ -54,6 +54,7 @@ pub struct Dashboard {
|
||||
selected_diff_summary: Option<String>,
|
||||
selected_diff_preview: Vec<String>,
|
||||
selected_diff_patch: Option<String>,
|
||||
selected_conflict_protocol: Option<String>,
|
||||
selected_merge_readiness: Option<worktree::MergeReadiness>,
|
||||
output_mode: OutputMode,
|
||||
selected_pane: Pane,
|
||||
@@ -94,6 +95,7 @@ enum Pane {
|
||||
enum OutputMode {
|
||||
SessionOutput,
|
||||
WorktreeDiff,
|
||||
ConflictProtocol,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -173,6 +175,7 @@ impl Dashboard {
|
||||
selected_diff_summary: None,
|
||||
selected_diff_preview: Vec::new(),
|
||||
selected_diff_patch: None,
|
||||
selected_conflict_protocol: None,
|
||||
selected_merge_readiness: None,
|
||||
output_mode: OutputMode::SessionOutput,
|
||||
selected_pane: Pane::Sessions,
|
||||
@@ -365,6 +368,16 @@ impl Dashboard {
|
||||
});
|
||||
(" Diff ", content)
|
||||
}
|
||||
OutputMode::ConflictProtocol => {
|
||||
let content = self
|
||||
.selected_conflict_protocol
|
||||
.clone()
|
||||
.unwrap_or_else(|| {
|
||||
"No conflicted worktree available for the selected session."
|
||||
.to_string()
|
||||
});
|
||||
(" Conflict Protocol ", content)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
(" Output ", "No sessions. Press 'n' to start one.".to_string())
|
||||
@@ -462,7 +475,7 @@ impl Dashboard {
|
||||
|
||||
fn render_status_bar(&self, frame: &mut Frame, area: Rect) {
|
||||
let text = format!(
|
||||
" [n]ew session [a]ssign re[b]alance global re[B]alance dra[i]n inbox [g]lobal dispatch coordinate [G]lobal [v]iew diff [m]erge merge ready [M] auto-merge [w] toggle [p]olicy [,/.] dispatch limit [s]top [u]resume [x]cleanup prune inactive [X] [d]elete [r]efresh [Tab] switch pane [j/k] scroll [+/-] resize [{}] layout [?] help [q]uit ",
|
||||
" [n]ew session [a]ssign re[b]alance global re[B]alance dra[i]n inbox [g]lobal dispatch coordinate [G]lobal [v]iew diff conflict proto[c]ol [m]erge merge ready [M] auto-merge [w] toggle [p]olicy [,/.] dispatch limit [s]top [u]resume [x]cleanup prune inactive [X] [d]elete [r]efresh [Tab] switch pane [j/k] scroll [+/-] resize [{}] layout [?] help [q]uit ",
|
||||
self.layout_label()
|
||||
);
|
||||
let text = if let Some(note) = self.operator_note.as_ref() {
|
||||
@@ -514,6 +527,7 @@ impl Dashboard {
|
||||
" g Auto-dispatch unread handoffs across lead sessions",
|
||||
" G Dispatch then rebalance backlog across lead teams",
|
||||
" v Toggle selected worktree diff in output pane",
|
||||
" c Show conflict-resolution protocol for selected conflicted worktree",
|
||||
" m Merge selected ready worktree into base and clean it up",
|
||||
" M Merge all ready inactive worktrees and clean them up",
|
||||
" p Toggle daemon auto-dispatch policy and persist config",
|
||||
@@ -727,6 +741,34 @@ impl Dashboard {
|
||||
self.reset_output_view();
|
||||
self.set_operator_note("showing session output".to_string());
|
||||
}
|
||||
OutputMode::ConflictProtocol => {
|
||||
self.output_mode = OutputMode::SessionOutput;
|
||||
self.reset_output_view();
|
||||
self.set_operator_note("showing session output".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_conflict_protocol_mode(&mut self) {
|
||||
match self.output_mode {
|
||||
OutputMode::ConflictProtocol => {
|
||||
self.output_mode = OutputMode::SessionOutput;
|
||||
self.reset_output_view();
|
||||
self.set_operator_note("showing session output".to_string());
|
||||
}
|
||||
_ => {
|
||||
if self.selected_conflict_protocol.is_some() {
|
||||
self.output_mode = OutputMode::ConflictProtocol;
|
||||
self.selected_pane = Pane::Output;
|
||||
self.output_follow = false;
|
||||
self.output_scroll_offset = 0;
|
||||
self.set_operator_note("showing worktree conflict protocol".to_string());
|
||||
} else {
|
||||
self.set_operator_note(
|
||||
"no conflicted worktree for selected session".to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1473,10 +1515,8 @@ impl Dashboard {
|
||||
}
|
||||
|
||||
fn sync_selected_diff(&mut self) {
|
||||
let worktree = self
|
||||
.sessions
|
||||
.get(self.selected_session)
|
||||
.and_then(|session| session.worktree.as_ref());
|
||||
let session = self.sessions.get(self.selected_session);
|
||||
let worktree = session.and_then(|session| session.worktree.as_ref());
|
||||
|
||||
self.selected_diff_summary =
|
||||
worktree.and_then(|worktree| worktree::diff_summary(worktree).ok().flatten());
|
||||
@@ -1487,9 +1527,20 @@ impl Dashboard {
|
||||
.and_then(|worktree| worktree::diff_patch_preview(worktree, MAX_DIFF_PATCH_LINES).ok().flatten());
|
||||
self.selected_merge_readiness = worktree
|
||||
.and_then(|worktree| worktree::merge_readiness(worktree).ok());
|
||||
self.selected_conflict_protocol = session
|
||||
.zip(worktree)
|
||||
.zip(self.selected_merge_readiness.as_ref())
|
||||
.and_then(|((session, worktree), merge_readiness)| {
|
||||
build_conflict_protocol(&session.id, worktree, merge_readiness)
|
||||
});
|
||||
if self.output_mode == OutputMode::WorktreeDiff && self.selected_diff_patch.is_none() {
|
||||
self.output_mode = OutputMode::SessionOutput;
|
||||
}
|
||||
if self.output_mode == OutputMode::ConflictProtocol
|
||||
&& self.selected_conflict_protocol.is_none()
|
||||
{
|
||||
self.output_mode = OutputMode::SessionOutput;
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_selected_messages(&mut self) {
|
||||
@@ -2410,6 +2461,44 @@ fn format_session_id(id: &str) -> String {
|
||||
id.chars().take(8).collect()
|
||||
}
|
||||
|
||||
fn build_conflict_protocol(
|
||||
session_id: &str,
|
||||
worktree: &crate::session::WorktreeInfo,
|
||||
merge_readiness: &worktree::MergeReadiness,
|
||||
) -> Option<String> {
|
||||
if merge_readiness.status != worktree::MergeReadinessStatus::Conflicted {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut lines = vec![
|
||||
format!("Conflict protocol for {}", format_session_id(session_id)),
|
||||
format!("Worktree {}", worktree.path.display()),
|
||||
format!("Branch {} (base {})", worktree.branch, worktree.base_branch),
|
||||
merge_readiness.summary.clone(),
|
||||
];
|
||||
|
||||
if !merge_readiness.conflicts.is_empty() {
|
||||
lines.push("Conflicts".to_string());
|
||||
for conflict in &merge_readiness.conflicts {
|
||||
lines.push(format!("- {conflict}"));
|
||||
}
|
||||
}
|
||||
|
||||
lines.push("Resolution steps".to_string());
|
||||
lines.push(format!(
|
||||
"1. Inspect current patch: ecc worktree-status {session_id} --patch"
|
||||
));
|
||||
lines.push(format!("2. Open worktree: cd {}", worktree.path.display()));
|
||||
lines.push("3. Resolve conflicts and stage files: git add <paths>".to_string());
|
||||
lines.push(format!("4. Commit the resolution on {}: git commit", worktree.branch));
|
||||
lines.push(format!(
|
||||
"5. Re-check readiness: ecc worktree-status {session_id} --check"
|
||||
));
|
||||
lines.push(format!("6. Merge when clear: ecc merge-worktree {session_id}"));
|
||||
|
||||
Some(lines.join("\n"))
|
||||
}
|
||||
|
||||
fn assignment_action_label(action: manager::AssignmentAction) -> &'static str {
|
||||
match action {
|
||||
manager::AssignmentAction::Spawned => "spawned",
|
||||
@@ -2566,6 +2655,41 @@ mod tests {
|
||||
assert!(rendered.contains("diff --git a/src/lib.rs b/src/lib.rs"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn toggle_conflict_protocol_mode_switches_to_protocol_view() {
|
||||
let mut dashboard = test_dashboard(
|
||||
vec![sample_session(
|
||||
"focus-12345678",
|
||||
"planner",
|
||||
SessionState::Running,
|
||||
Some("ecc/focus"),
|
||||
512,
|
||||
42,
|
||||
)],
|
||||
0,
|
||||
);
|
||||
dashboard.selected_merge_readiness = Some(worktree::MergeReadiness {
|
||||
status: worktree::MergeReadinessStatus::Conflicted,
|
||||
summary: "Merge blocked by 1 conflict(s): src/main.rs".to_string(),
|
||||
conflicts: vec!["src/main.rs".to_string()],
|
||||
});
|
||||
dashboard.selected_conflict_protocol = Some(
|
||||
"Conflict protocol for focus-12\nResolution steps\n1. Inspect current patch: ecc worktree-status focus-12345678 --patch"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
dashboard.toggle_conflict_protocol_mode();
|
||||
|
||||
assert_eq!(dashboard.output_mode, OutputMode::ConflictProtocol);
|
||||
assert_eq!(
|
||||
dashboard.operator_note.as_deref(),
|
||||
Some("showing worktree conflict protocol")
|
||||
);
|
||||
let rendered = dashboard.rendered_output_text(180, 30);
|
||||
assert!(rendered.contains("Conflict Protocol"));
|
||||
assert!(rendered.contains("Resolution steps"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selected_session_metrics_text_includes_team_capacity_summary() {
|
||||
let mut dashboard = test_dashboard(
|
||||
@@ -3762,6 +3886,7 @@ mod tests {
|
||||
selected_diff_summary: None,
|
||||
selected_diff_preview: Vec::new(),
|
||||
selected_diff_patch: None,
|
||||
selected_conflict_protocol: None,
|
||||
selected_merge_readiness: None,
|
||||
output_mode: OutputMode::SessionOutput,
|
||||
selected_pane: Pane::Sessions,
|
||||
|
||||
Reference in New Issue
Block a user