huskies: merge 910

This commit is contained in:
dave
2026-05-12 15:54:10 +00:00
parent e65f6ace84
commit 916dc2b11d
2 changed files with 163 additions and 0 deletions
@@ -496,6 +496,127 @@ async fn watchdog_kill_preserves_uncommitted_diff() {
);
}
/// Story 910 regression: a coder that exits with zero commits on the feature
/// branch must NOT be promoted to Merge. The server-owned completion path
/// detects `git rev-list master..HEAD == 0`, records `gates_passed=false`,
/// and the pipeline advance retries (or blocks at the cap) instead of
/// advancing the story.
#[tokio::test]
async fn zero_commit_coder_exit_stays_in_coding_not_promoted_to_merge() {
use std::fs;
use std::process::Command;
let tmp = tempfile::tempdir().unwrap();
let project_root = tmp.path().join("project");
fs::create_dir_all(&project_root).unwrap();
// Init a git repo with an initial master commit.
Command::new("git")
.args(["init"])
.current_dir(&project_root)
.output()
.unwrap();
Command::new("git")
.args(["config", "user.email", "test@test.com"])
.current_dir(&project_root)
.output()
.unwrap();
Command::new("git")
.args(["config", "user.name", "Test"])
.current_dir(&project_root)
.output()
.unwrap();
Command::new("git")
.args(["commit", "--allow-empty", "-m", "init"])
.current_dir(&project_root)
.output()
.unwrap();
// Create a feature-branch worktree with ZERO commits ahead of master.
let wt_path = tmp.path().join("wt");
Command::new("git")
.args([
"worktree",
"add",
&wt_path.to_string_lossy(),
"-b",
"feature/story-9910_zero_exit",
])
.current_dir(&project_root)
.output()
.unwrap();
// Set up the story with max_retries=1 so it blocks on the first failure.
crate::crdt_state::init_for_test();
crate::db::ensure_content_store();
crate::db::write_item_with_content(
"9910_zero_exit",
"2_current",
"---\nname: Zero Exit Test\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: Zero Exit Test\n---\n"),
);
fs::create_dir_all(project_root.join(".huskies")).unwrap();
fs::write(
project_root.join(".huskies/project.toml"),
"max_retries = 1\n\n[[agent]]\nname = \"coder-1\"\nstage = \"coder\"\n",
)
.unwrap();
let pool = AgentPool::new_test(3001);
pool.inject_test_agent_with_root_and_path(
"9910_zero_exit",
"coder-1",
AgentStatus::Running,
project_root.clone(),
wt_path.clone(),
);
let mut rx = pool.watcher_tx.subscribe();
run_server_owned_completion(
&pool.agents,
pool.port,
"9910_zero_exit",
"coder-1",
None,
pool.watcher_tx.clone(),
)
.await;
// The pipeline advance spawns asynchronously — poll with a timeout.
let mut got_blocked = false;
let deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(5);
while tokio::time::Instant::now() < deadline {
while let Ok(evt) = rx.try_recv() {
if let crate::io::watcher::WatcherEvent::StoryBlocked { story_id, .. } = &evt
&& story_id == "9910_zero_exit"
{
got_blocked = true;
}
}
if got_blocked {
break;
}
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
}
assert!(
got_blocked,
"Story 910 regression: a zero-commit coder exit must block/retry \
the story rather than advancing it to Merge"
);
// The story must NOT be in 4_merge.
if let Ok(Some(item)) = crate::pipeline_state::read_typed("9910_zero_exit") {
assert_ne!(
item.stage.dir_name(),
"4_merge",
"Story must NOT be in Merge after a zero-commit coder exit"
);
}
}
/// AC4 (bug 651 regression for 645): when an agent crashes with committed
/// work AND uncommitted noise, the auto-advance still picks up the
/// committed work. The committed-state check is authoritative; the