huskies: merge 1178 bug MergeFailureFinal is a trap state: successful re-merge cannot mark the story done
This commit is contained in:
@@ -5,30 +5,41 @@
|
||||
|
||||
use crate::agents::merge::{MergeReport, MergeResult};
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// Derive a human-readable status message from a completed [`MergeReport`].
|
||||
///
|
||||
/// The message explains what happened and (on failure) what the caller
|
||||
/// should do next.
|
||||
pub fn format_merge_status_message(report: &MergeReport) -> &'static str {
|
||||
/// should do next. On success, the wording reflects what
|
||||
/// [`MergeReport::story_archived`] and [`MergeReport::worktree_cleaned_up`]
|
||||
/// actually recorded — story 1178: the message must never claim "moved to
|
||||
/// done" when the CRDT state transition to `Done` failed.
|
||||
pub fn format_merge_status_message(report: &MergeReport) -> String {
|
||||
match &report.result {
|
||||
MergeResult::Success {
|
||||
conflicts_resolved: true,
|
||||
..
|
||||
conflicts_resolved, ..
|
||||
} => {
|
||||
"Merge complete: conflicts were auto-resolved and all quality gates passed. Story moved to done and worktree cleaned up."
|
||||
}
|
||||
MergeResult::Success { .. } => {
|
||||
"Merge complete: all quality gates passed. Story moved to done and worktree cleaned up."
|
||||
let prefix = if *conflicts_resolved {
|
||||
"Merge complete: conflicts were auto-resolved and all quality gates passed."
|
||||
} else {
|
||||
"Merge complete: all quality gates passed."
|
||||
};
|
||||
match (report.story_archived, report.worktree_cleaned_up) {
|
||||
(true, true) => format!("{prefix} Story moved to done and worktree cleaned up."),
|
||||
(true, false) => {
|
||||
format!("{prefix} Story moved to done, but worktree cleanup failed.")
|
||||
}
|
||||
(false, _) => format!(
|
||||
"{prefix} The merge landed on master, but the story could not be moved to done — it needs manual follow-up."
|
||||
),
|
||||
}
|
||||
}
|
||||
MergeResult::Conflict { .. } => {
|
||||
"Merge failed: conflicts detected that could not be auto-resolved. Merge was aborted — master is untouched. Call report_merge_failure with the conflict details so the human can resolve them. Do NOT manually move the story file or call accept_story."
|
||||
"Merge failed: conflicts detected that could not be auto-resolved. Merge was aborted — master is untouched. Call report_merge_failure with the conflict details so the human can resolve them. Do NOT manually move the story file or call accept_story.".to_string()
|
||||
}
|
||||
MergeResult::GateFailure { .. } => {
|
||||
"Merge committed but quality gates failed. Review gate_output and fix issues before re-running."
|
||||
"Merge committed but quality gates failed. Review gate_output and fix issues before re-running.".to_string()
|
||||
}
|
||||
MergeResult::NoCommits { .. } | MergeResult::Other { .. } => {
|
||||
"Merge failed. Review gate_output for details. Call report_merge_failure to record the failure. Do NOT manually move the story file or call accept_story."
|
||||
"Merge failed. Review gate_output for details. Call report_merge_failure to record the failure. Do NOT manually move the story file or call accept_story.".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,25 +62,48 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn clean_merge_message() {
|
||||
let mut r = make_report(MergeResult::Success {
|
||||
conflicts_resolved: false,
|
||||
conflict_details: None,
|
||||
gate_output: String::new(),
|
||||
});
|
||||
r.story_archived = true;
|
||||
r.worktree_cleaned_up = true;
|
||||
let msg = format_merge_status_message(&r);
|
||||
assert!(msg.contains("quality gates passed"));
|
||||
assert!(msg.contains("moved to done"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conflicts_resolved_message() {
|
||||
let mut r = make_report(MergeResult::Success {
|
||||
conflicts_resolved: true,
|
||||
conflict_details: None,
|
||||
gate_output: String::new(),
|
||||
});
|
||||
r.story_archived = true;
|
||||
r.worktree_cleaned_up = true;
|
||||
let msg = format_merge_status_message(&r);
|
||||
assert!(msg.contains("auto-resolved"));
|
||||
}
|
||||
|
||||
/// Regression test (story 1178): a git-level merge success whose CRDT
|
||||
/// state transition to Done failed (`story_archived: false`) must never
|
||||
/// produce a message claiming the story was "moved to done".
|
||||
#[test]
|
||||
fn success_without_archival_does_not_claim_moved_to_done() {
|
||||
let r = make_report(MergeResult::Success {
|
||||
conflicts_resolved: false,
|
||||
conflict_details: None,
|
||||
gate_output: String::new(),
|
||||
});
|
||||
assert!(!r.story_archived);
|
||||
let msg = format_merge_status_message(&r);
|
||||
assert!(msg.contains("quality gates passed"));
|
||||
assert!(msg.contains("done"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conflicts_resolved_message() {
|
||||
let r = make_report(MergeResult::Success {
|
||||
conflicts_resolved: true,
|
||||
conflict_details: None,
|
||||
gate_output: String::new(),
|
||||
});
|
||||
let msg = format_merge_status_message(&r);
|
||||
assert!(msg.contains("auto-resolved"));
|
||||
assert!(
|
||||
!msg.contains("Story moved to done"),
|
||||
"message must not claim the story moved to done when story_archived is false: {msg}"
|
||||
);
|
||||
assert!(msg.contains("could not be moved to done"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user