mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-10 19:33:37 +08:00
feat: add ecc2 global worktree status
This commit is contained in:
205
ecc2/src/main.rs
205
ecc2/src/main.rs
@@ -190,6 +190,9 @@ enum Commands {
|
|||||||
WorktreeStatus {
|
WorktreeStatus {
|
||||||
/// Session ID or alias
|
/// Session ID or alias
|
||||||
session_id: Option<String>,
|
session_id: Option<String>,
|
||||||
|
/// Show worktree status for all sessions
|
||||||
|
#[arg(long)]
|
||||||
|
all: bool,
|
||||||
/// Emit machine-readable JSON instead of the human summary
|
/// Emit machine-readable JSON instead of the human summary
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
json: bool,
|
json: bool,
|
||||||
@@ -645,23 +648,40 @@ async fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
Some(Commands::WorktreeStatus {
|
Some(Commands::WorktreeStatus {
|
||||||
session_id,
|
session_id,
|
||||||
|
all,
|
||||||
json,
|
json,
|
||||||
patch,
|
patch,
|
||||||
check,
|
check,
|
||||||
}) => {
|
}) => {
|
||||||
let id = session_id.unwrap_or_else(|| "latest".to_string());
|
if all && session_id.is_some() {
|
||||||
let resolved_id = resolve_session_id(&db, &id)?;
|
return Err(anyhow::anyhow!(
|
||||||
let session = db
|
"worktree-status does not accept a session ID when --all is set"
|
||||||
.get_session(&resolved_id)?
|
));
|
||||||
.ok_or_else(|| anyhow::anyhow!("Session not found: {resolved_id}"))?;
|
}
|
||||||
let report = build_worktree_status_report(&session, patch)?;
|
let reports = if all {
|
||||||
if json {
|
session::manager::list_sessions(&db)?
|
||||||
println!("{}", serde_json::to_string_pretty(&report)?);
|
.into_iter()
|
||||||
|
.map(|session| build_worktree_status_report(&session, patch))
|
||||||
|
.collect::<Result<Vec<_>>>()?
|
||||||
} else {
|
} else {
|
||||||
println!("{}", format_worktree_status_human(&report));
|
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_status_report(&session, patch)?]
|
||||||
|
};
|
||||||
|
if json {
|
||||||
|
if all {
|
||||||
|
println!("{}", serde_json::to_string_pretty(&reports)?);
|
||||||
|
} else {
|
||||||
|
println!("{}", serde_json::to_string_pretty(&reports[0])?);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("{}", format_worktree_status_reports_human(&reports));
|
||||||
}
|
}
|
||||||
if check {
|
if check {
|
||||||
std::process::exit(worktree_status_exit_code(&report));
|
std::process::exit(worktree_status_reports_exit_code(&reports));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(Commands::Stop { session_id }) => {
|
Some(Commands::Stop { session_id }) => {
|
||||||
@@ -1011,10 +1031,26 @@ fn format_worktree_status_human(report: &WorktreeStatusReport) -> String {
|
|||||||
lines.join("\n")
|
lines.join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn format_worktree_status_reports_human(reports: &[WorktreeStatusReport]) -> String {
|
||||||
|
reports
|
||||||
|
.iter()
|
||||||
|
.map(format_worktree_status_human)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
fn worktree_status_exit_code(report: &WorktreeStatusReport) -> i32 {
|
fn worktree_status_exit_code(report: &WorktreeStatusReport) -> i32 {
|
||||||
report.check_exit_code
|
report.check_exit_code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn worktree_status_reports_exit_code(reports: &[WorktreeStatusReport]) -> i32 {
|
||||||
|
reports
|
||||||
|
.iter()
|
||||||
|
.map(worktree_status_exit_code)
|
||||||
|
.max()
|
||||||
|
.unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
fn summarize_coordinate_backlog(
|
fn summarize_coordinate_backlog(
|
||||||
outcome: &session::manager::CoordinateBacklogOutcome,
|
outcome: &session::manager::CoordinateBacklogOutcome,
|
||||||
) -> CoordinateBacklogPassSummary {
|
) -> CoordinateBacklogPassSummary {
|
||||||
@@ -1250,11 +1286,13 @@ mod tests {
|
|||||||
match cli.command {
|
match cli.command {
|
||||||
Some(Commands::WorktreeStatus {
|
Some(Commands::WorktreeStatus {
|
||||||
session_id,
|
session_id,
|
||||||
|
all,
|
||||||
json,
|
json,
|
||||||
patch,
|
patch,
|
||||||
check,
|
check,
|
||||||
}) => {
|
}) => {
|
||||||
assert_eq!(session_id.as_deref(), Some("planner"));
|
assert_eq!(session_id.as_deref(), Some("planner"));
|
||||||
|
assert!(!all);
|
||||||
assert!(!json);
|
assert!(!json);
|
||||||
assert!(!patch);
|
assert!(!patch);
|
||||||
assert!(!check);
|
assert!(!check);
|
||||||
@@ -1271,11 +1309,13 @@ mod tests {
|
|||||||
match cli.command {
|
match cli.command {
|
||||||
Some(Commands::WorktreeStatus {
|
Some(Commands::WorktreeStatus {
|
||||||
session_id,
|
session_id,
|
||||||
|
all,
|
||||||
json,
|
json,
|
||||||
patch,
|
patch,
|
||||||
check,
|
check,
|
||||||
}) => {
|
}) => {
|
||||||
assert_eq!(session_id, None);
|
assert_eq!(session_id, None);
|
||||||
|
assert!(!all);
|
||||||
assert!(json);
|
assert!(json);
|
||||||
assert!(!patch);
|
assert!(!patch);
|
||||||
assert!(!check);
|
assert!(!check);
|
||||||
@@ -1284,6 +1324,91 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cli_parses_worktree_status_all_flag() {
|
||||||
|
let cli = Cli::try_parse_from(["ecc", "worktree-status", "--all"])
|
||||||
|
.expect("worktree-status --all should parse");
|
||||||
|
|
||||||
|
match cli.command {
|
||||||
|
Some(Commands::WorktreeStatus {
|
||||||
|
session_id,
|
||||||
|
all,
|
||||||
|
json,
|
||||||
|
patch,
|
||||||
|
check,
|
||||||
|
}) => {
|
||||||
|
assert_eq!(session_id, None);
|
||||||
|
assert!(all);
|
||||||
|
assert!(!json);
|
||||||
|
assert!(!patch);
|
||||||
|
assert!(!check);
|
||||||
|
}
|
||||||
|
_ => panic!("expected worktree-status subcommand"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cli_parses_worktree_status_session_id_with_all_flag() {
|
||||||
|
let err = Cli::try_parse_from(["ecc", "worktree-status", "planner", "--all"])
|
||||||
|
.expect("worktree-status planner --all should parse");
|
||||||
|
|
||||||
|
let command = err.command.expect("expected command");
|
||||||
|
let Commands::WorktreeStatus {
|
||||||
|
session_id,
|
||||||
|
all,
|
||||||
|
..
|
||||||
|
} = command
|
||||||
|
else {
|
||||||
|
panic!("expected worktree-status subcommand");
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(session_id.as_deref(), Some("planner"));
|
||||||
|
assert!(all);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_worktree_status_reports_human_joins_multiple_reports() {
|
||||||
|
let reports = vec![
|
||||||
|
WorktreeStatusReport {
|
||||||
|
session_id: "sess-a".to_string(),
|
||||||
|
task: "first".to_string(),
|
||||||
|
session_state: "running".to_string(),
|
||||||
|
health: "in_progress".to_string(),
|
||||||
|
check_exit_code: 1,
|
||||||
|
patch_included: false,
|
||||||
|
attached: false,
|
||||||
|
path: None,
|
||||||
|
branch: None,
|
||||||
|
base_branch: None,
|
||||||
|
diff_summary: None,
|
||||||
|
file_preview: Vec::new(),
|
||||||
|
patch_preview: None,
|
||||||
|
merge_readiness: None,
|
||||||
|
},
|
||||||
|
WorktreeStatusReport {
|
||||||
|
session_id: "sess-b".to_string(),
|
||||||
|
task: "second".to_string(),
|
||||||
|
session_state: "stopped".to_string(),
|
||||||
|
health: "clear".to_string(),
|
||||||
|
check_exit_code: 0,
|
||||||
|
patch_included: false,
|
||||||
|
attached: false,
|
||||||
|
path: None,
|
||||||
|
branch: None,
|
||||||
|
base_branch: None,
|
||||||
|
diff_summary: None,
|
||||||
|
file_preview: Vec::new(),
|
||||||
|
patch_preview: None,
|
||||||
|
merge_readiness: None,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let text = format_worktree_status_reports_human(&reports);
|
||||||
|
assert!(text.contains("Worktree status for sess-a [running]"));
|
||||||
|
assert!(text.contains("Worktree status for sess-b [stopped]"));
|
||||||
|
assert!(text.contains("\n\nWorktree status for sess-b [stopped]"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cli_parses_worktree_status_patch_flag() {
|
fn cli_parses_worktree_status_patch_flag() {
|
||||||
let cli = Cli::try_parse_from(["ecc", "worktree-status", "--patch"])
|
let cli = Cli::try_parse_from(["ecc", "worktree-status", "--patch"])
|
||||||
@@ -1292,11 +1417,13 @@ mod tests {
|
|||||||
match cli.command {
|
match cli.command {
|
||||||
Some(Commands::WorktreeStatus {
|
Some(Commands::WorktreeStatus {
|
||||||
session_id,
|
session_id,
|
||||||
|
all,
|
||||||
json,
|
json,
|
||||||
patch,
|
patch,
|
||||||
check,
|
check,
|
||||||
}) => {
|
}) => {
|
||||||
assert_eq!(session_id, None);
|
assert_eq!(session_id, None);
|
||||||
|
assert!(!all);
|
||||||
assert!(!json);
|
assert!(!json);
|
||||||
assert!(patch);
|
assert!(patch);
|
||||||
assert!(!check);
|
assert!(!check);
|
||||||
@@ -1313,11 +1440,13 @@ mod tests {
|
|||||||
match cli.command {
|
match cli.command {
|
||||||
Some(Commands::WorktreeStatus {
|
Some(Commands::WorktreeStatus {
|
||||||
session_id,
|
session_id,
|
||||||
|
all,
|
||||||
json,
|
json,
|
||||||
patch,
|
patch,
|
||||||
check,
|
check,
|
||||||
}) => {
|
}) => {
|
||||||
assert_eq!(session_id, None);
|
assert_eq!(session_id, None);
|
||||||
|
assert!(!all);
|
||||||
assert!(!json);
|
assert!(!json);
|
||||||
assert!(!patch);
|
assert!(!patch);
|
||||||
assert!(check);
|
assert!(check);
|
||||||
@@ -1450,6 +1579,62 @@ mod tests {
|
|||||||
assert_eq!(worktree_status_exit_code(&conflicted), 2);
|
assert_eq!(worktree_status_exit_code(&conflicted), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn worktree_status_reports_exit_code_uses_highest_severity() {
|
||||||
|
let reports = vec![
|
||||||
|
WorktreeStatusReport {
|
||||||
|
session_id: "sess-a".to_string(),
|
||||||
|
task: "first".to_string(),
|
||||||
|
session_state: "running".to_string(),
|
||||||
|
health: "clear".to_string(),
|
||||||
|
check_exit_code: 0,
|
||||||
|
patch_included: false,
|
||||||
|
attached: false,
|
||||||
|
path: None,
|
||||||
|
branch: None,
|
||||||
|
base_branch: None,
|
||||||
|
diff_summary: None,
|
||||||
|
file_preview: Vec::new(),
|
||||||
|
patch_preview: None,
|
||||||
|
merge_readiness: None,
|
||||||
|
},
|
||||||
|
WorktreeStatusReport {
|
||||||
|
session_id: "sess-b".to_string(),
|
||||||
|
task: "second".to_string(),
|
||||||
|
session_state: "running".to_string(),
|
||||||
|
health: "in_progress".to_string(),
|
||||||
|
check_exit_code: 1,
|
||||||
|
patch_included: false,
|
||||||
|
attached: false,
|
||||||
|
path: None,
|
||||||
|
branch: None,
|
||||||
|
base_branch: None,
|
||||||
|
diff_summary: None,
|
||||||
|
file_preview: Vec::new(),
|
||||||
|
patch_preview: None,
|
||||||
|
merge_readiness: None,
|
||||||
|
},
|
||||||
|
WorktreeStatusReport {
|
||||||
|
session_id: "sess-c".to_string(),
|
||||||
|
task: "third".to_string(),
|
||||||
|
session_state: "running".to_string(),
|
||||||
|
health: "conflicted".to_string(),
|
||||||
|
check_exit_code: 2,
|
||||||
|
patch_included: false,
|
||||||
|
attached: false,
|
||||||
|
path: None,
|
||||||
|
branch: None,
|
||||||
|
base_branch: None,
|
||||||
|
diff_summary: None,
|
||||||
|
file_preview: Vec::new(),
|
||||||
|
patch_preview: None,
|
||||||
|
merge_readiness: None,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
assert_eq!(worktree_status_reports_exit_code(&reports), 2);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cli_parses_assign_command() {
|
fn cli_parses_assign_command() {
|
||||||
let cli = Cli::try_parse_from([
|
let cli = Cli::try_parse_from([
|
||||||
|
|||||||
Reference in New Issue
Block a user