huskies: merge 864

This commit is contained in:
dave
2026-04-30 22:23:21 +00:00
parent 3911c24c26
commit 61cf7684de
41 changed files with 540 additions and 71 deletions
@@ -57,7 +57,12 @@ mod tests {
.unwrap();
// Place the story in 2_current/ via CRDT (the only source of truth).
crate::db::ensure_content_store();
crate::db::write_item_with_content("story-3", "2_current", "---\nname: Story 3\n---\n");
crate::db::write_item_with_content(
"story-3",
"2_current",
"---\nname: Story 3\n---\n",
crate::db::ItemMeta::named("Story 3"),
);
let pool = AgentPool::new_test(3001);
// No agents are running — coder-1 is free.
@@ -139,6 +144,11 @@ mod tests {
"9930_story_qa1",
"3_qa",
"---\nname: QA Story\nagent: coder-1\n---\n",
crate::db::ItemMeta {
name: Some("QA Story".into()),
agent: Some("coder-1".into()),
..Default::default()
},
);
let pool = AgentPool::new_test(3001);
@@ -188,6 +198,11 @@ mod tests {
"story-pref",
"2_current",
"---\nname: Coder Story\nagent: coder-1\n---\n",
crate::db::ItemMeta {
name: Some("Coder Story".into()),
agent: Some("coder-1".into()),
..Default::default()
},
);
let pool = AgentPool::new_test(3001);
@@ -235,6 +250,11 @@ mod tests {
"9931_story_noqa",
"3_qa",
"---\nname: QA Story No Agent\nagent: coder-1\n---\n",
crate::db::ItemMeta {
name: Some("QA Story No Agent".into()),
agent: Some("coder-1".into()),
..Default::default()
},
);
let pool = AgentPool::new_test(3001);
@@ -274,6 +294,7 @@ mod tests {
"9932_story_waiting",
"2_current",
"---\nname: Waiting\ndepends_on: [9999]\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: Waiting\ndepends_on: [9999]\n---\n"),
);
let pool = AgentPool::new_test(3001);
@@ -307,12 +328,18 @@ mod tests {
// Seed stories via CRDT (the only source of truth).
crate::db::ensure_content_store();
// Dep 999 is now done.
crate::db::write_item_with_content("999_story_dep", "5_done", "---\nname: Dep\n---\n");
crate::db::write_item_with_content(
"999_story_dep",
"5_done",
"---\nname: Dep\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: Dep\n---\n"),
);
// Story 10 depends on 999 which is done.
crate::db::write_item_with_content(
"10_story_unblocked",
"2_current",
"---\nname: Unblocked\ndepends_on: [999]\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: Unblocked\ndepends_on: [999]\n---\n"),
);
let pool = AgentPool::new_test(3001);
@@ -496,6 +523,9 @@ mod tests {
"9860_story_conflict",
"4_merge",
"---\nname: Conflict\nmerge_failure: \"CONFLICT (content): server/src/lib.rs\"\n---\n",
crate::db::ItemMeta::from_yaml(
"---\nname: Conflict\nmerge_failure: \"CONFLICT (content): server/src/lib.rs\"\n---\n",
),
);
let pool = AgentPool::new_test(3001);
@@ -530,6 +560,9 @@ mod tests {
"9861_story_nothing",
"4_merge",
"---\nname: Nothing\nmerge_failure: \"nothing to commit, working tree clean\"\n---\n",
crate::db::ItemMeta::from_yaml(
"---\nname: Nothing\nmerge_failure: \"nothing to commit, working tree clean\"\n---\n",
),
);
let pool = AgentPool::new_test(3001);
@@ -565,6 +598,9 @@ mod tests {
"9863_story_blocked_conflict",
"4_merge",
"---\nname: Blocked conflict\nmerge_failure: \"CONFLICT (content): foo.rs\"\nblocked: true\n---\n",
crate::db::ItemMeta::from_yaml(
"---\nname: Blocked conflict\nmerge_failure: \"CONFLICT (content): foo.rs\"\nblocked: true\n---\n",
),
);
let pool = AgentPool::new_test(3001);
@@ -599,6 +635,9 @@ mod tests {
"9862_story_attempted",
"4_merge",
"---\nname: Already tried\nmerge_failure: \"CONFLICT (content): foo.rs\"\nmergemaster_attempted: true\n---\n",
crate::db::ItemMeta::from_yaml(
"---\nname: Already tried\nmerge_failure: \"CONFLICT (content): foo.rs\"\nmergemaster_attempted: true\n---\n",
),
);
let pool = AgentPool::new_test(3001);
+6 -1
View File
@@ -83,7 +83,12 @@ impl AgentPool {
&contents,
);
crate::db::write_content(story_id, &updated);
crate::db::write_item_with_content(story_id, "4_merge", &updated);
crate::db::write_item_with_content(
story_id,
"4_merge",
&updated,
crate::db::ItemMeta::from_yaml(&updated),
);
}
crate::crdt_state::set_mergemaster_attempted(story_id, true);
if let Err(e) = self
+19 -3
View File
@@ -168,6 +168,7 @@ mod tests {
"9970_story_archived",
"6_archived",
"---\nname: Archived\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: Archived\n---\n"),
);
// Also place a stale .md file in a temp 1_backlog/ dir.
@@ -200,9 +201,24 @@ mod tests {
fn scan_stage_items_returns_sorted_story_ids() {
// Write items via the CRDT store (the primary source of truth).
crate::db::ensure_content_store();
crate::db::write_item_with_content("9942_story_foo", "2_current", "---\nname: foo\n---");
crate::db::write_item_with_content("9940_story_bar", "2_current", "---\nname: bar\n---");
crate::db::write_item_with_content("9935_story_baz", "2_current", "---\nname: baz\n---");
crate::db::write_item_with_content(
"9942_story_foo",
"2_current",
"---\nname: foo\n---",
crate::db::ItemMeta::from_yaml("---\nname: foo\n---"),
);
crate::db::write_item_with_content(
"9940_story_bar",
"2_current",
"---\nname: bar\n---",
crate::db::ItemMeta::from_yaml("---\nname: bar\n---"),
);
crate::db::write_item_with_content(
"9935_story_baz",
"2_current",
"---\nname: baz\n---",
crate::db::ItemMeta::from_yaml("---\nname: baz\n---"),
);
let tmp = tempfile::tempdir().unwrap();
let items = scan_stage_items(tmp.path(), "2_current");
@@ -188,6 +188,9 @@ mod tests {
"10_spike_research",
"3_qa",
"---\nname: Research spike\nreview_hold: true\n---\n# Spike\n",
crate::db::ItemMeta::from_yaml(
"---\nname: Research spike\nreview_hold: true\n---\n# Spike\n",
),
);
assert!(has_review_hold(tmp.path(), "3_qa", "10_spike_research"));
}
@@ -88,7 +88,12 @@ pub(super) fn write_review_hold_to_store(story_id: &str) {
.flatten()
.map(|i| i.stage.dir_name().to_string())
.unwrap_or_else(|| "3_qa".to_string());
crate::db::write_item_with_content(story_id, &stage, &updated);
crate::db::write_item_with_content(
story_id,
&stage,
&updated,
crate::db::ItemMeta::from_yaml(&updated),
);
} else {
slog_error!("[pipeline] Cannot write review_hold for '{story_id}': no content in store");
}
@@ -172,7 +172,12 @@ async fn pipeline_advance_sends_agent_state_changed_to_watcher_tx() {
// Seed story via CRDT (the only source of truth).
crate::db::ensure_content_store();
crate::db::write_item_with_content("173_story_test", "2_current", "---\nname: test\n---\n");
crate::db::write_item_with_content(
"173_story_test",
"2_current",
"---\nname: test\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: test\n---\n"),
);
// Write a project.toml with a qa agent so start_agent can resolve it.
fs::create_dir_all(root.join(".huskies")).unwrap();
@@ -51,6 +51,7 @@ async fn mergemaster_blocks_and_sends_story_blocked_when_no_commits_ahead() {
"9919_story_no_commits",
"2_current",
"---\nname: Test\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: Test\n---\n"),
);
let pool = AgentPool::new_test(3001);
@@ -145,11 +146,13 @@ stage = "qa"
"292_story_first",
"3_qa",
"---\nname: First\nqa: human\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: First\nqa: human\n---\n"),
);
crate::db::write_item_with_content(
"293_story_second",
"3_qa",
"---\nname: Second\nqa: human\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: Second\nqa: human\n---\n"),
);
let pool = AgentPool::new_test(3001);
@@ -245,7 +248,12 @@ async fn stale_mergemaster_advance_for_done_story_is_noop() {
let story_id = "9929_story_zombie_merge";
let content = "---\nname: Zombie Merge Test\n---\n";
crate::db::write_content(story_id, content);
crate::db::write_item_with_content(story_id, "5_done", content);
crate::db::write_item_with_content(
story_id,
"5_done",
content,
crate::db::ItemMeta::from_yaml(content),
);
let pool = AgentPool::new_test(3001);
let mut rx = pool.watcher_tx.subscribe();
@@ -381,6 +389,7 @@ async fn work_survived_advances_to_qa_instead_of_blocking() {
"9945_story_survived",
"2_current",
"---\nname: Survived Test\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: Survived Test\n---\n"),
);
// Simulate a passing run_tests call during the agent's session (bug 668):
@@ -474,6 +483,7 @@ async fn no_committed_work_still_retries_and_blocks() {
"9946_story_nowork",
"2_current",
"---\nname: No Work Test\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: No Work Test\n---\n"),
);
// Write a project.toml with max_retries = 1.
@@ -601,6 +611,7 @@ async fn gates_failed_no_test_evidence_does_not_advance() {
"9947_story_no_evidence",
"2_current",
"---\nname: No Evidence Test\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: No Evidence Test\n---\n"),
);
// Explicitly ensure no test evidence exists for this story.
@@ -730,6 +741,7 @@ async fn gates_failed_with_test_evidence_and_committed_work_advances() {
"9948_story_with_evidence",
"2_current",
"---\nname: With Evidence Test\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: With Evidence Test\n---\n"),
);
// Write the run_tests evidence — simulates the agent having called run_tests
@@ -813,6 +825,7 @@ stage = "coder"
"9950_story_warm_resume",
"2_current",
"---\nname: Warm Resume Test\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: Warm Resume Test\n---\n"),
);
let pool = AgentPool::new_test(3001);
@@ -651,6 +651,7 @@ async fn server_side_merge_happy_path_advances_to_done() {
"757a_happy",
"4_merge",
"---\nname: Happy path test\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: Happy path test\n---\n"),
);
let pool = Arc::new(AgentPool::new_test(3001));
@@ -787,6 +788,7 @@ async fn server_side_merge_conflict_sets_merge_failure() {
"757b_conflict",
"4_merge",
"---\nname: Conflict test\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: Conflict test\n---\n"),
);
let pool = Arc::new(AgentPool::new_test(3001));
@@ -898,6 +900,7 @@ async fn server_side_merge_gate_failure_sets_merge_failure() {
"757c_gates",
"4_merge",
"---\nname: Gate failure test\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: Gate failure test\n---\n"),
);
let pool = Arc::new(AgentPool::new_test(3001));
+18 -3
View File
@@ -597,7 +597,12 @@ mod tests {
crate::db::ensure_content_store();
let story_id = "9960_story_gate_injection_881";
crate::db::write_item_with_content(story_id, "2_current", "---\nname: Test\n---\n");
crate::db::write_item_with_content(
story_id,
"2_current",
"---\nname: Test\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: Test\n---\n"),
);
crate::crdt_state::set_retry_count(story_id, 1);
let gate_output =
@@ -630,7 +635,12 @@ mod tests {
crate::db::ensure_content_store();
let story_id = "9961_story_no_gate_injection_881";
crate::db::write_item_with_content(story_id, "2_current", "---\nname: Test\n---\n");
crate::db::write_item_with_content(
story_id,
"2_current",
"---\nname: Test\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: Test\n---\n"),
);
// retry_count is 0 (default — never bumped).
crate::db::write_content(&format!("{story_id}:gate_output"), "some previous output");
@@ -690,7 +700,12 @@ mod tests {
crate::db::ensure_content_store();
let story_id = "9962_story_abort_respawn_882";
crate::db::write_item_with_content(story_id, "2_current", "---\nname: Test\n---\n");
crate::db::write_item_with_content(
story_id,
"2_current",
"---\nname: Test\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: Test\n---\n"),
);
let db_key = format!("{story_id}:abort_respawn_count");
const CAP: u32 = 5;
@@ -429,7 +429,12 @@ async fn start_agent_rejects_mergemaster_on_coding_stage_story() {
)
.unwrap();
crate::db::ensure_content_store();
crate::db::write_item_with_content("310_story_foo", "2_current", "---\nname: Foo\n---\n");
crate::db::write_item_with_content(
"310_story_foo",
"2_current",
"---\nname: Foo\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: Foo\n---\n"),
);
let pool = AgentPool::new_test(3099);
let result = pool
@@ -463,7 +468,12 @@ async fn start_agent_rejects_coder_on_qa_stage_story() {
)
.unwrap();
crate::db::ensure_content_store();
crate::db::write_item_with_content("8842_story_qa_guard", "3_qa", "---\nname: QA Guard\n---\n");
crate::db::write_item_with_content(
"8842_story_qa_guard",
"3_qa",
"---\nname: QA Guard\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: QA Guard\n---\n"),
);
let pool = AgentPool::new_test(3099);
let result = pool
@@ -497,7 +507,12 @@ async fn start_agent_rejects_qa_on_merge_stage_story() {
)
.unwrap();
crate::db::ensure_content_store();
crate::db::write_item_with_content("55_story_baz", "4_merge", "---\nname: Baz\n---\n");
crate::db::write_item_with_content(
"55_story_baz",
"4_merge",
"---\nname: Baz\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: Baz\n---\n"),
);
let pool = AgentPool::new_test(3099);
let result = pool
+6 -1
View File
@@ -143,7 +143,12 @@ mod tests {
let story_content = "test";
fs::write(current.join("60_story_cleanup.md"), story_content).unwrap();
crate::db::ensure_content_store();
crate::db::write_item_with_content("60_story_cleanup", "2_current", story_content);
crate::db::write_item_with_content(
"60_story_cleanup",
"2_current",
story_content,
crate::db::ItemMeta::from_yaml(story_content),
);
let pool = AgentPool::new_test(3001);
pool.inject_test_agent("60_story_cleanup", "coder-1", AgentStatus::Completed);
+18 -3
View File
@@ -42,7 +42,12 @@ mod tests {
#[test]
fn find_active_story_stage_detects_current() {
crate::db::ensure_content_store();
crate::db::write_item_with_content("10_story_test", "2_current", "---\nname: Test\n---\n");
crate::db::write_item_with_content(
"10_story_test",
"2_current",
"---\nname: Test\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: Test\n---\n"),
);
let tmp = tempfile::tempdir().unwrap();
assert!(matches!(
find_active_story_stage(tmp.path(), "10_story_test"),
@@ -53,7 +58,12 @@ mod tests {
#[test]
fn find_active_story_stage_detects_qa() {
crate::db::ensure_content_store();
crate::db::write_item_with_content("11_story_test", "3_qa", "---\nname: Test\n---\n");
crate::db::write_item_with_content(
"11_story_test",
"3_qa",
"---\nname: Test\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: Test\n---\n"),
);
let tmp = tempfile::tempdir().unwrap();
assert!(matches!(
find_active_story_stage(tmp.path(), "11_story_test"),
@@ -64,7 +74,12 @@ mod tests {
#[test]
fn find_active_story_stage_detects_merge() {
crate::db::ensure_content_store();
crate::db::write_item_with_content("12_story_test", "4_merge", "---\nname: Test\n---\n");
crate::db::write_item_with_content(
"12_story_test",
"4_merge",
"---\nname: Test\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: Test\n---\n"),
);
let tmp = tempfile::tempdir().unwrap();
assert!(matches!(
find_active_story_stage(tmp.path(), "12_story_test"),