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
+2
View File
@@ -326,6 +326,7 @@ async fn get_work_item_content_falls_back_to_crdt_when_no_file() {
"44_story_crdt_only",
"1_backlog",
"---\nname: \"CRDT Only\"\n---\n\nCRDT content.",
crate::db::ItemMeta::from_yaml("---\nname: \"CRDT Only\"\n---\n\nCRDT content."),
);
let ctx = AppContext::new_test(root);
let api = AgentsApi { ctx: Arc::new(ctx) };
@@ -348,6 +349,7 @@ async fn get_work_item_content_crdt_fallback_with_current_stage() {
"45_story_crdt_current",
"2_current",
"---\nname: \"Current CRDT\"\n---\n\nIn progress.",
crate::db::ItemMeta::from_yaml("---\nname: \"Current CRDT\"\n---\n\nIn progress."),
);
let ctx = AppContext::new_test(root);
let api = AgentsApi { ctx: Arc::new(ctx) };
+18 -3
View File
@@ -449,7 +449,12 @@ mod tests {
let content = "---\nname: Test\n---\n";
fs::write(backlog.join("5_story_test.md"), content).unwrap();
crate::db::ensure_content_store();
crate::db::write_item_with_content("5_story_test", "1_backlog", content);
crate::db::write_item_with_content(
"5_story_test",
"1_backlog",
content,
crate::db::ItemMeta::from_yaml(content),
);
let ctx = test_ctx(root);
let result = super::super::tool_move_story(
@@ -476,7 +481,12 @@ mod tests {
let content = "---\nname: Back\n---\n";
fs::write(current.join("6_story_back.md"), content).unwrap();
crate::db::ensure_content_store();
crate::db::write_item_with_content("6_story_back", "2_current", content);
crate::db::write_item_with_content(
"6_story_back",
"2_current",
content,
crate::db::ItemMeta::from_yaml(content),
);
let ctx = test_ctx(root);
let result = super::super::tool_move_story(
@@ -503,7 +513,12 @@ mod tests {
let content = "---\nname: Idem\n---\n";
fs::write(current.join("9907_story_idem.md"), content).unwrap();
crate::db::ensure_content_store();
crate::db::write_item_with_content("9907_story_idem", "2_current", content);
crate::db::write_item_with_content(
"9907_story_idem",
"2_current",
content,
crate::db::ItemMeta::from_yaml(content),
);
let ctx = test_ctx(root);
let result = super::super::tool_move_story(
+12 -2
View File
@@ -366,7 +366,12 @@ mod tests {
crate::crdt_state::init_for_test();
crate::db::ensure_content_store();
let story_content = "---\nname: Blocked Story\nblocked: true\nretry_count: 3\ndepends_on: [100, 200]\n---\n\n## Acceptance Criteria\n\n- [ ] Do the thing\n";
crate::db::write_item_with_content("9887_story_blocked_test", "2_current", story_content);
crate::db::write_item_with_content(
"9887_story_blocked_test",
"2_current",
story_content,
crate::db::ItemMeta::from_yaml(story_content),
);
let ctx = crate::http::context::AppContext::new_test(tmp.path().to_path_buf());
let result = tool_status(&json!({"story_id": "9887_story_blocked_test"}), &ctx)
@@ -388,7 +393,12 @@ mod tests {
crate::db::ensure_content_store();
let story_content = "---\nname: My Test Story\nagent: coder-1\n---\n\n## Acceptance Criteria\n\n- [ ] First criterion\n- [x] Second criterion\n\n## Out of Scope\n\n- nothing\n";
crate::db::write_item_with_content("9886_story_status_test", "2_current", story_content);
crate::db::write_item_with_content(
"9886_story_status_test",
"2_current",
story_content,
crate::db::ItemMeta::from_yaml(story_content),
);
let ctx = crate::http::context::AppContext::new_test(tmp.path().to_path_buf());
let result = tool_status(&json!({"story_id": "9886_story_status_test"}), &ctx)
+12 -1
View File
@@ -290,11 +290,17 @@ mod tests {
"9902_bug_crash",
"1_backlog",
"---\nname: \"App Crash\"\n---\n# Bug 9902: App Crash\n",
crate::db::ItemMeta::from_yaml(
"---\nname: \"App Crash\"\n---\n# Bug 9902: App Crash\n",
),
);
crate::db::write_item_with_content(
"9903_bug_typo",
"1_backlog",
"---\nname: \"Typo in Header\"\n---\n# Bug 9903: Typo in Header\n",
crate::db::ItemMeta::from_yaml(
"---\nname: \"Typo in Header\"\n---\n# Bug 9903: Typo in Header\n",
),
);
let ctx = test_ctx(tmp.path());
@@ -438,7 +444,12 @@ mod tests {
let content = "# Bug 9901: Crash\n";
std::fs::write(&bug_file, content).unwrap();
crate::db::ensure_content_store();
crate::db::write_item_with_content("9901_bug_crash", "1_backlog", content);
crate::db::write_item_with_content(
"9901_bug_crash",
"1_backlog",
content,
crate::db::ItemMeta::from_yaml(content),
);
// Stage the file so it's tracked
std::process::Command::new("git")
.args(["add", "."])
+22 -1
View File
@@ -421,6 +421,9 @@ mod tests {
"9901_test",
"2_current",
"---\nname: Test\n---\n## AC\n- [ ] First\n- [x] Done\n- [ ] Second\n",
crate::db::ItemMeta::from_yaml(
"---\nname: Test\n---\n## AC\n- [ ] First\n- [x] Done\n- [ ] Second\n",
),
);
let ctx = test_ctx(tmp.path());
@@ -514,6 +517,7 @@ mod tests {
"9906_story_persist",
"2_current",
"---\nname: Persist\n---\n# Story\n",
crate::db::ItemMeta::from_yaml("---\nname: Persist\n---\n# Story\n"),
);
let ctx = test_ctx(tmp.path());
@@ -547,7 +551,12 @@ mod tests {
// Write story content to CRDT with a pre-populated Test Results section
let story_content = "---\nname: Persist\n---\n# Story\n\n## Test Results\n\n<!-- huskies-test-results: {\"unit\":[{\"name\":\"u1\",\"status\":\"pass\",\"details\":null}],\"integration\":[{\"name\":\"i1\",\"status\":\"pass\",\"details\":null}]} -->\n";
crate::db::ensure_content_store();
crate::db::write_item_with_content("9905_story_file_only", "2_current", story_content);
crate::db::write_item_with_content(
"9905_story_file_only",
"2_current",
story_content,
crate::db::ItemMeta::from_yaml(story_content),
);
let ctx = test_ctx(tmp.path());
@@ -618,6 +627,9 @@ mod tests {
"9997_empty_branch",
"2_current",
"---\nname: Empty Branch Test\n---\n## AC\n- [ ] Implement the feature\n",
crate::db::ItemMeta::from_yaml(
"---\nname: Empty Branch Test\n---\n## AC\n- [ ] Implement the feature\n",
),
);
let ctx = test_ctx(tmp.path());
@@ -672,6 +684,9 @@ mod tests {
"9904_test",
"2_current",
"---\nname: Test\n---\n## AC\n- [ ] First criterion\n- [x] Already done\n",
crate::db::ItemMeta::from_yaml(
"---\nname: Test\n---\n## AC\n- [ ] First criterion\n- [x] Already done\n",
),
);
let ctx = test_ctx(tmp.path());
@@ -711,6 +726,9 @@ mod tests {
"9905_test",
"2_current",
"---\nname: Test\n---\n## Acceptance Criteria\n- [ ] Keep me\n- [ ] Remove me\n",
crate::db::ItemMeta::from_yaml(
"---\nname: Test\n---\n## Acceptance Criteria\n- [ ] Keep me\n- [ ] Remove me\n",
),
);
let ctx = test_ctx(tmp.path());
@@ -732,6 +750,9 @@ mod tests {
"9906_test",
"2_current",
"---\nname: Test\n---\n## Acceptance Criteria\n- [ ] Only one\n",
crate::db::ItemMeta::from_yaml(
"---\nname: Test\n---\n## Acceptance Criteria\n- [ ] Only one\n",
),
);
let ctx = test_ctx(tmp.path());
+9
View File
@@ -246,17 +246,26 @@ mod tests {
"9990_epic_rollup",
"1_backlog",
"---\ntype: epic\nname: \"Rollup Epic\"\n---\n\n## Goal\n\nTest\n",
crate::db::ItemMeta::from_yaml(
"---\ntype: epic\nname: \"Rollup Epic\"\n---\n\n## Goal\n\nTest\n",
),
);
// Write two member items: one done, one current.
crate::db::write_item_with_content(
"9991_story_member_done",
"5_done",
"---\ntype: story\nname: \"Done Member\"\nepic: \"9990_epic_rollup\"\n---\n",
crate::db::ItemMeta::from_yaml(
"---\ntype: story\nname: \"Done Member\"\nepic: \"9990_epic_rollup\"\n---\n",
),
);
crate::db::write_item_with_content(
"9992_story_member_current",
"2_current",
"---\ntype: story\nname: \"Current Member\"\nepic: \"9990_epic_rollup\"\n---\n",
crate::db::ItemMeta::from_yaml(
"---\ntype: story\nname: \"Current Member\"\nepic: \"9990_epic_rollup\"\n---\n",
),
);
let tmp = tempfile::tempdir().unwrap();
@@ -216,7 +216,12 @@ mod tests {
let content = "---\nname: No Branch\n---\n";
std::fs::write(current_dir.join("51_story_no_branch.md"), content).unwrap();
crate::db::ensure_content_store();
crate::db::write_item_with_content("51_story_no_branch", "2_current", content);
crate::db::write_item_with_content(
"51_story_no_branch",
"2_current",
content,
crate::db::ItemMeta::from_yaml(content),
);
let ctx = test_ctx(tmp.path());
let result = tool_accept_story(&json!({"story_id": "51_story_no_branch"}), &ctx);
@@ -60,6 +60,7 @@ mod tests {
story_id,
"2_current",
"---\nname: MCP Freeze Tool Test\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: MCP Freeze Tool Test\n---\n"),
);
let tmp = tempfile::tempdir().unwrap();
@@ -88,6 +89,7 @@ mod tests {
story_id,
"2_current",
"---\nname: MCP Unfreeze Tool Test\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: MCP Unfreeze Tool Test\n---\n"),
);
let tmp = tempfile::tempdir().unwrap();
+14 -2
View File
@@ -150,7 +150,12 @@ mod tests {
("4_merge", "9940_story_merge", "Merge Story"),
("5_done", "9950_story_done", "Done Story"),
] {
crate::db::write_item_with_content(id, stage, &format!("---\nname: \"{name}\"\n---\n"));
crate::db::write_item_with_content(
id,
stage,
&format!("---\nname: \"{name}\"\n---\n"),
crate::db::ItemMeta::from_yaml(&format!("---\nname: \"{name}\"\n---\n")),
);
}
let ctx = test_ctx(tmp.path());
@@ -187,6 +192,7 @@ mod tests {
"9921_story_active",
"2_current",
"---\nname: \"Active Story\"\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: \"Active Story\"\n---\n"),
);
let ctx = test_ctx(tmp.path());
@@ -219,6 +225,7 @@ mod tests {
"9907_test",
"2_current",
"---\nname: \"Valid Story\"\n---\n## AC\n- [ ] First\n",
crate::db::ItemMeta::from_yaml("---\nname: \"Valid Story\"\n---\n## AC\n- [ ] First\n"),
);
let ctx = test_ctx(tmp.path());
@@ -236,7 +243,12 @@ mod tests {
let tmp = tempfile::tempdir().unwrap();
crate::db::ensure_content_store();
crate::db::write_item_with_content("9908_test", "2_current", "## No front matter at all\n");
crate::db::write_item_with_content(
"9908_test",
"2_current",
"## No front matter at all\n",
crate::db::ItemMeta::from_yaml("## No front matter at all\n"),
);
let ctx = test_ctx(tmp.path());
let result = tool_validate_stories(&ctx).unwrap();
@@ -150,7 +150,12 @@ mod tests {
fs::create_dir_all(&current).unwrap();
fs::write(current.join(format!("{story_id}.md")), content).unwrap();
crate::db::ensure_content_store();
crate::db::write_item_with_content(story_id, "2_current", content);
crate::db::write_item_with_content(
story_id,
"2_current",
content,
crate::db::ItemMeta::from_yaml(content),
);
}
#[test]
+24 -3
View File
@@ -46,8 +46,18 @@ fn next_item_number_increments_from_existing_bugs() {
fs::write(backlog.join("1_bug_crash.md"), "").unwrap();
fs::write(backlog.join("3_bug_another.md"), "").unwrap();
// Also write to content store so next_item_number sees them.
crate::db::write_item_with_content("1_bug_crash", "1_backlog", "---\nname: Crash\n---\n");
crate::db::write_item_with_content("3_bug_another", "1_backlog", "---\nname: Another\n---\n");
crate::db::write_item_with_content(
"1_bug_crash",
"1_backlog",
"---\nname: Crash\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: Crash\n---\n"),
);
crate::db::write_item_with_content(
"3_bug_another",
"1_backlog",
"---\nname: Another\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: Another\n---\n"),
);
assert!(super::super::next_item_number(tmp.path()).unwrap() >= 4);
}
@@ -61,7 +71,12 @@ fn next_item_number_scans_archived_too() {
fs::create_dir_all(&archived).unwrap();
fs::write(archived.join("5_bug_old.md"), "").unwrap();
// Also write to content store so next_item_number sees it.
crate::db::write_item_with_content("5_bug_old", "5_done", "---\nname: Old Bug\n---\n");
crate::db::write_item_with_content(
"5_bug_old",
"5_done",
"---\nname: Old Bug\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: Old Bug\n---\n"),
);
assert!(super::super::next_item_number(tmp.path()).unwrap() >= 6);
}
@@ -83,12 +98,14 @@ fn list_bug_files_excludes_archive_subdir() {
"7001_bug_open",
"1_backlog",
"---\nname: Open Bug\n---\n# Bug 7001: Open Bug\n",
crate::db::ItemMeta::from_yaml("---\nname: Open Bug\n---\n# Bug 7001: Open Bug\n"),
);
// Bug in done (should NOT appear — list_bug_files only returns Backlog).
crate::db::write_item_with_content(
"7002_bug_closed",
"5_done",
"---\nname: Closed Bug\n---\n# Bug 7002: Closed Bug\n",
crate::db::ItemMeta::from_yaml("---\nname: Closed Bug\n---\n# Bug 7002: Closed Bug\n"),
);
let result = list_bug_files(tmp.path()).unwrap();
@@ -108,16 +125,19 @@ fn list_bug_files_sorted_by_id() {
"7013_bug_third",
"1_backlog",
"---\nname: Third\n---\n# Bug 7013: Third\n",
crate::db::ItemMeta::from_yaml("---\nname: Third\n---\n# Bug 7013: Third\n"),
);
crate::db::write_item_with_content(
"7011_bug_first",
"1_backlog",
"---\nname: First\n---\n# Bug 7011: First\n",
crate::db::ItemMeta::from_yaml("---\nname: First\n---\n# Bug 7011: First\n"),
);
crate::db::write_item_with_content(
"7012_bug_second",
"1_backlog",
"---\nname: Second\n---\n# Bug 7012: Second\n",
crate::db::ItemMeta::from_yaml("---\nname: Second\n---\n# Bug 7012: Second\n"),
);
let result = list_bug_files(tmp.path()).unwrap();
@@ -349,6 +369,7 @@ fn create_spike_file_increments_from_existing_items() {
"7050_story_existing",
"1_backlog",
"---\nname: Existing\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: Existing\n---\n"),
);
let spike_id = create_spike_file(tmp.path(), "My Spike", None, &[], None).unwrap();
+25 -2
View File
@@ -325,7 +325,12 @@ mod tests {
("4_merge", "9840_story_merge"),
("5_done", "9850_story_done"),
] {
crate::db::write_item_with_content(id, stage, &format!("---\nname: {id}\n---\n"));
crate::db::write_item_with_content(
id,
stage,
&format!("---\nname: {id}\n---\n"),
crate::db::ItemMeta::from_yaml(&format!("---\nname: {id}\n---\n")),
);
}
let ctx = crate::http::context::AppContext::new_test(root);
@@ -369,6 +374,7 @@ mod tests {
"9860_story_test",
"2_current",
"---\nname: Test Story\n---\n# Story\n",
crate::db::ItemMeta::from_yaml("---\nname: Test Story\n---\n# Story\n"),
);
let ctx = crate::http::context::AppContext::new_test(root);
@@ -404,6 +410,7 @@ mod tests {
"9861_story_done",
"2_current",
"---\nname: Done Story\n---\n# Story\n",
crate::db::ItemMeta::from_yaml("---\nname: Done Story\n---\n# Story\n"),
);
let ctx = crate::http::context::AppContext::new_test(root);
@@ -436,6 +443,7 @@ mod tests {
"9862_story_pending",
"2_current",
"---\nname: Pending Story\n---\n# Story\n",
crate::db::ItemMeta::from_yaml("---\nname: Pending Story\n---\n# Story\n"),
);
let ctx = crate::http::context::AppContext::new_test(root);
@@ -466,11 +474,15 @@ mod tests {
"9863_story_dependent",
"1_backlog",
"---\nname: Dependent Story\ndepends_on: [10, 11]\n---\n",
crate::db::ItemMeta::from_yaml(
"---\nname: Dependent Story\ndepends_on: [10, 11]\n---\n",
),
);
crate::db::write_item_with_content(
"9864_story_independent",
"1_backlog",
"---\nname: Independent Story\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: Independent Story\n---\n"),
);
let tmp = tempfile::tempdir().unwrap();
@@ -499,11 +511,13 @@ mod tests {
"9870_story_view_upcoming",
"1_backlog",
"---\nname: View Upcoming\n---\n# Story\n",
crate::db::ItemMeta::from_yaml("---\nname: View Upcoming\n---\n# Story\n"),
);
crate::db::write_item_with_content(
"9871_story_worktree",
"1_backlog",
"---\nname: Worktree Orchestration\n---\n# Story\n",
crate::db::ItemMeta::from_yaml("---\nname: Worktree Orchestration\n---\n# Story\n"),
);
let tmp = tempfile::tempdir().unwrap();
@@ -530,6 +544,7 @@ mod tests {
"9872_story_example",
"1_backlog",
"---\nname: A Story\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: A Story\n---\n"),
);
let tmp = tempfile::tempdir().unwrap();
@@ -545,11 +560,13 @@ mod tests {
"9873_story_todos",
"2_current",
"---\nname: Show TODOs\n---\n# Story\n",
crate::db::ItemMeta::from_yaml("---\nname: Show TODOs\n---\n# Story\n"),
);
crate::db::write_item_with_content(
"9874_story_front_matter",
"1_backlog",
"---\nname: Enforce Front Matter\n---\n# Story\n",
crate::db::ItemMeta::from_yaml("---\nname: Enforce Front Matter\n---\n# Story\n"),
);
let tmp = tempfile::tempdir().unwrap();
@@ -569,7 +586,12 @@ mod tests {
#[test]
fn validate_story_dirs_missing_front_matter() {
crate::db::ensure_content_store();
crate::db::write_item_with_content("9875_story_no_fm", "2_current", "# No front matter\n");
crate::db::write_item_with_content(
"9875_story_no_fm",
"2_current",
"# No front matter\n",
crate::db::ItemMeta::from_yaml("# No front matter\n"),
);
let tmp = tempfile::tempdir().unwrap();
let results = validate_story_dirs(tmp.path()).unwrap();
@@ -588,6 +610,7 @@ mod tests {
"9876_story_no_name",
"2_current",
"---\n---\n# Story\n",
crate::db::ItemMeta::from_yaml("---\n---\n# Story\n"),
);
let tmp = tempfile::tempdir().unwrap();
@@ -143,6 +143,7 @@ mod tests {
"36_story_existing",
"1_backlog",
"---\nname: Existing\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: Existing\n---\n"),
);
let number = super::super::super::next_item_number(tmp.path()).unwrap();
+10
View File
@@ -175,6 +175,7 @@ mod tests {
"8001_story_test",
"2_current",
"---\nname: Test\n---\n# Story\n",
crate::db::ItemMeta::from_yaml("---\nname: Test\n---\n# Story\n"),
);
let results = make_results();
@@ -200,6 +201,9 @@ mod tests {
"8002_story_check",
"2_current",
"---\nname: Check\n---\n# Story\n\n## Acceptance Criteria\n\n- [ ] AC1\n",
crate::db::ItemMeta::from_yaml(
"---\nname: Check\n---\n# Story\n\n## Acceptance Criteria\n\n- [ ] AC1\n",
),
);
let results = make_results();
@@ -222,6 +226,9 @@ mod tests {
"8003_story_overwrite",
"2_current",
"---\nname: Overwrite\n---\n# Story\n\n## Test Results\n\n<!-- huskies-test-results: {} -->\n\n### Unit Tests (0 passed, 0 failed)\n\n*No unit tests recorded.*\n",
crate::db::ItemMeta::from_yaml(
"---\nname: Overwrite\n---\n# Story\n\n## Test Results\n\n<!-- huskies-test-results: {} -->\n\n### Unit Tests (0 passed, 0 failed)\n\n*No unit tests recorded.*\n",
),
);
let results = make_results();
@@ -241,6 +248,7 @@ mod tests {
"8004_story_empty",
"2_current",
"---\nname: Empty\n---\n# Story\n",
crate::db::ItemMeta::from_yaml("---\nname: Empty\n---\n# Story\n"),
);
let result = read_test_results_from_story_file(tmp.path(), "8004_story_empty");
@@ -262,6 +270,7 @@ mod tests {
"8005_story_qa",
"3_qa",
"---\nname: QA Story\n---\n# Story\n",
crate::db::ItemMeta::from_yaml("---\nname: QA Story\n---\n# Story\n"),
);
let results = StoryTestResults {
@@ -286,6 +295,7 @@ mod tests {
"8006_story_cov",
"2_current",
"---\nname: Cov Story\n---\n# Story\n",
crate::db::ItemMeta::from_yaml("---\nname: Cov Story\n---\n# Story\n"),
);
write_coverage_baseline_to_story_file(tmp.path(), "8006_story_cov", 75.4).unwrap();
+12 -2
View File
@@ -17,7 +17,12 @@ pub(crate) fn write_story_content(
stage: &str,
content: &str,
) {
crate::db::write_item_with_content(story_id, stage, content);
crate::db::write_item_with_content(
story_id,
stage,
content,
crate::db::ItemMeta::from_yaml(content),
);
}
/// Determine what stage a story is in (from CRDT).
@@ -262,7 +267,12 @@ mod tests {
#[test]
fn next_item_number_increments_beyond_existing() {
crate::db::ensure_content_store();
crate::db::write_item_with_content("9877_story_foo", "1_backlog", "---\nname: Foo\n---\n");
crate::db::write_item_with_content(
"9877_story_foo",
"1_backlog",
"---\nname: Foo\n---\n",
crate::db::ItemMeta::from_yaml("---\nname: Foo\n---\n"),
);
let tmp = tempfile::tempdir().unwrap();
assert!(next_item_number(tmp.path()).unwrap() >= 9878);
}