huskies: merge 1188 story Merge tool results return a summary, not the full gate log (35KB per call)
This commit is contained in:
@@ -82,9 +82,15 @@ pub(super) fn tool_get_merge_status(args: &Value, ctx: &AppContext) -> Result<St
|
||||
|
||||
match &job.status {
|
||||
crate::agents::merge::MergeJobStatus::Running => {
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs_f64();
|
||||
let elapsed_seconds = (now - job.started_at).max(0.0);
|
||||
serde_json::to_string_pretty(&json!({
|
||||
"story_id": story_id,
|
||||
"status": "running",
|
||||
"elapsed_seconds": elapsed_seconds,
|
||||
"message": "Merge pipeline is still running."
|
||||
}))
|
||||
.map_err(|e| format!("Serialization error: {e}"))
|
||||
@@ -92,24 +98,27 @@ pub(super) fn tool_get_merge_status(args: &Value, ctx: &AppContext) -> Result<St
|
||||
crate::agents::merge::MergeJobStatus::Completed(report) => {
|
||||
use crate::agents::merge::MergeResult;
|
||||
let status_msg = crate::service::merge::format_merge_status_message(report);
|
||||
let (success, had_conflicts, conflicts_resolved, conflict_details, gates_passed, gate_output) =
|
||||
let (success, had_conflicts, conflicts_resolved, conflict_details, gates_passed) =
|
||||
match &report.result {
|
||||
MergeResult::Success { conflicts_resolved, conflict_details, gate_output } => {
|
||||
(true, *conflicts_resolved, *conflicts_resolved, conflict_details.clone(), true, gate_output.clone())
|
||||
MergeResult::Success { conflicts_resolved, conflict_details, .. } => {
|
||||
(true, *conflicts_resolved, *conflicts_resolved, conflict_details.clone(), true)
|
||||
}
|
||||
MergeResult::Conflict { details, output } => {
|
||||
(false, true, false, details.clone(), false, output.clone())
|
||||
MergeResult::Conflict { details, .. } => {
|
||||
(false, true, false, details.clone(), false)
|
||||
}
|
||||
MergeResult::GateFailure { output, .. } => {
|
||||
(false, false, false, None, false, output.clone())
|
||||
MergeResult::GateFailure { .. } => {
|
||||
(false, false, false, None, false)
|
||||
}
|
||||
MergeResult::NoCommits { output } => {
|
||||
(false, false, false, None, false, output.clone())
|
||||
MergeResult::NoCommits { .. } => {
|
||||
(false, false, false, None, false)
|
||||
}
|
||||
MergeResult::Other { output, conflict_details } => {
|
||||
(false, false, false, conflict_details.clone(), false, output.clone())
|
||||
MergeResult::Other { conflict_details, .. } => {
|
||||
(false, false, false, conflict_details.clone(), false)
|
||||
}
|
||||
};
|
||||
// AC1: condensed digest instead of the full (potentially 35KB+)
|
||||
// gate_output — successful merges carry no summary at all.
|
||||
let gate_summary = crate::service::merge::summarize_merge_result(&report.result);
|
||||
|
||||
serde_json::to_string_pretty(&json!({
|
||||
"story_id": story_id,
|
||||
@@ -119,7 +128,8 @@ pub(super) fn tool_get_merge_status(args: &Value, ctx: &AppContext) -> Result<St
|
||||
"conflicts_resolved": conflicts_resolved,
|
||||
"conflict_details": conflict_details,
|
||||
"gates_passed": gates_passed,
|
||||
"gate_output": gate_output,
|
||||
"gate_summary": if gate_summary.is_empty() { serde_json::Value::Null } else { json!(gate_summary) },
|
||||
"report_path": report.report_path,
|
||||
"worktree_cleaned_up": report.worktree_cleaned_up,
|
||||
"story_archived": report.story_archived,
|
||||
"message": status_msg,
|
||||
@@ -398,6 +408,118 @@ mod tests {
|
||||
assert!(result.unwrap_err().contains("No merge job"));
|
||||
}
|
||||
|
||||
/// AC3: a still-running merge job returns status and elapsed time only —
|
||||
/// no gate data of any kind belongs on a Running response.
|
||||
#[test]
|
||||
fn tool_get_merge_status_running_includes_elapsed_seconds_only() {
|
||||
crate::crdt_state::init_for_test();
|
||||
crate::crdt_state::write_merge_job(
|
||||
"50_story_running",
|
||||
"running",
|
||||
0.0,
|
||||
None,
|
||||
Some("{\"server_start\":0.0}"),
|
||||
);
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let ctx = test_ctx(tmp.path());
|
||||
let result = tool_get_merge_status(&json!({"story_id": "50_story_running"}), &ctx);
|
||||
assert!(result.is_ok(), "expected Ok, got: {result:?}");
|
||||
let v: serde_json::Value = serde_json::from_str(&result.unwrap()).unwrap();
|
||||
assert_eq!(v["status"], "running");
|
||||
assert!(
|
||||
v["elapsed_seconds"].as_f64().unwrap() > 0.0,
|
||||
"elapsed_seconds must reflect time since started_at=0.0: {v}"
|
||||
);
|
||||
assert!(
|
||||
v.get("gate_output").is_none() && v.get("gate_summary").is_none(),
|
||||
"a running job must carry no gate data: {v}"
|
||||
);
|
||||
}
|
||||
|
||||
/// AC1: a successful merge's response carries no gate summary at all.
|
||||
#[test]
|
||||
fn tool_get_merge_status_completed_success_has_no_gate_summary() {
|
||||
crate::crdt_state::init_for_test();
|
||||
let report = crate::agents::merge::MergeReport {
|
||||
story_id: "51_story_ok".to_string(),
|
||||
result: crate::agents::merge::MergeResult::Success {
|
||||
conflicts_resolved: false,
|
||||
conflict_details: None,
|
||||
gate_output: "all tests passed".to_string(),
|
||||
},
|
||||
worktree_cleaned_up: true,
|
||||
story_archived: true,
|
||||
report_path: Some(".huskies/merge_reports/51_story_ok-1.log".to_string()),
|
||||
};
|
||||
let report_json = serde_json::to_string(&report).unwrap();
|
||||
crate::crdt_state::write_merge_job(
|
||||
"51_story_ok",
|
||||
"completed",
|
||||
1.0,
|
||||
Some(2.0),
|
||||
Some(&report_json),
|
||||
);
|
||||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let ctx = test_ctx(tmp.path());
|
||||
let result = tool_get_merge_status(&json!({"story_id": "51_story_ok"}), &ctx).unwrap();
|
||||
let v: serde_json::Value = serde_json::from_str(&result).unwrap();
|
||||
assert!(
|
||||
v["gate_summary"].is_null(),
|
||||
"success must carry no gate summary: {v}"
|
||||
);
|
||||
assert_eq!(v["report_path"], ".huskies/merge_reports/51_story_ok-1.log");
|
||||
}
|
||||
|
||||
/// AC1/AC2: a gate-failure response carries a condensed summary (failing
|
||||
/// gate + parsed test names + tail), plus the full-report path — not the
|
||||
/// raw multi-KB output.
|
||||
#[test]
|
||||
fn tool_get_merge_status_completed_gate_failure_has_condensed_summary() {
|
||||
crate::crdt_state::init_for_test();
|
||||
let output = "running 1 test\ntest foo::bar ... FAILED\n\n\
|
||||
failures:\n foo::bar\n\ntest result: FAILED. 0 passed; 1 failed\n"
|
||||
.to_string();
|
||||
let report = crate::agents::merge::MergeReport {
|
||||
story_id: "52_story_fail".to_string(),
|
||||
result: crate::agents::merge::MergeResult::GateFailure {
|
||||
output,
|
||||
failure_kind: Some(crate::agents::gates::GateFailureKind::Test),
|
||||
},
|
||||
worktree_cleaned_up: false,
|
||||
story_archived: false,
|
||||
report_path: Some(".huskies/merge_reports/52_story_fail-1.log".to_string()),
|
||||
};
|
||||
let report_json = serde_json::to_string(&report).unwrap();
|
||||
crate::crdt_state::write_merge_job(
|
||||
"52_story_fail",
|
||||
"completed",
|
||||
1.0,
|
||||
Some(2.0),
|
||||
Some(&report_json),
|
||||
);
|
||||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let ctx = test_ctx(tmp.path());
|
||||
let result = tool_get_merge_status(&json!({"story_id": "52_story_fail"}), &ctx).unwrap();
|
||||
let v: serde_json::Value = serde_json::from_str(&result).unwrap();
|
||||
let summary = v["gate_summary"]
|
||||
.as_str()
|
||||
.expect("gate_summary must be present on failure");
|
||||
assert!(
|
||||
summary.contains("Failing gate: tests"),
|
||||
"summary: {summary}"
|
||||
);
|
||||
assert!(
|
||||
summary.contains("Failing tests: foo::bar"),
|
||||
"summary: {summary}"
|
||||
);
|
||||
assert_eq!(
|
||||
v["report_path"],
|
||||
".huskies/merge_reports/52_story_fail-1.log"
|
||||
);
|
||||
}
|
||||
|
||||
// tool_get_merge_status_returns_running removed: depends on
|
||||
// tool_merge_agent_work which now blocks indefinitely in a poll loop.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user