huskies: merge 557_refactor_remove_all_filesystem_fallback_paths_crdt_is_the_only_source_of_truth

This commit is contained in:
dave
2026-04-14 09:10:14 +00:00
parent 10d3517648
commit 979cf39228
5 changed files with 67 additions and 88 deletions
@@ -35,16 +35,11 @@ pub fn extract_story_number(item_id: &str) -> Option<&str> {
.filter(|s| !s.is_empty() && s.chars().all(|c| c.is_ascii_digit()))
}
/// Read the story name from the work item file's YAML front matter.
/// Read the story name from the CRDT content store's YAML front matter.
///
/// Returns `None` if the file doesn't exist or has no parseable name.
pub fn read_story_name(project_root: &Path, stage: &str, item_id: &str) -> Option<String> {
let path = project_root
.join(".huskies")
.join("work")
.join(stage)
.join(format!("{item_id}.md"));
let contents = std::fs::read_to_string(&path).ok()?;
/// Returns `None` if the item is not in the content store or has no parseable name.
pub fn read_story_name(_project_root: &Path, _stage: &str, item_id: &str) -> Option<String> {
let contents = crate::db::read_content(item_id)?;
let meta = parse_front_matter(&contents).ok()?;
meta.name
}
@@ -85,18 +80,11 @@ pub fn format_error_notification(
(plain, html)
}
/// Search all pipeline stages for a story name.
/// Look up a story name from the CRDT content store.
///
/// Tries each known pipeline stage directory in order and returns the first
/// name found. Used for events (like rate-limit warnings) that arrive without
/// a known stage.
/// Used for events (like rate-limit warnings) that arrive without a known stage.
fn find_story_name_any_stage(project_root: &Path, item_id: &str) -> Option<String> {
for stage in &["2_current", "3_qa", "4_merge", "1_backlog", "5_done"] {
if let Some(name) = read_story_name(project_root, stage, item_id) {
return Some(name);
}
}
None
read_story_name(project_root, "", item_id)
}
/// Format a blocked-story notification message.
@@ -432,13 +420,13 @@ mod tests {
#[tokio::test]
async fn rate_limit_warning_sends_notification_with_agent_and_story() {
let tmp = tempfile::tempdir().unwrap();
let stage_dir = tmp.path().join(".huskies").join("work").join("2_current");
std::fs::create_dir_all(&stage_dir).unwrap();
std::fs::write(
stage_dir.join("365_story_rate_limit.md"),
// Seed story via CRDT (the only source of truth).
crate::db::ensure_content_store();
crate::db::write_item_with_content(
"365_story_rate_limit",
"2_current",
"---\nname: Rate Limit Test Story\n---\n",
)
.unwrap();
);
let (watcher_tx, watcher_rx) = broadcast::channel::<WatcherEvent>(16);
let (transport, calls) = MockTransport::new();
@@ -560,13 +548,9 @@ mod tests {
#[tokio::test]
async fn stage_notification_uses_dynamic_room_ids() {
let tmp = tempfile::tempdir().unwrap();
let stage_dir = tmp.path().join(".huskies").join("work").join("3_qa");
std::fs::create_dir_all(&stage_dir).unwrap();
std::fs::write(
stage_dir.join("10_story_foo.md"),
"---\nname: Foo Story\n---\n",
)
.unwrap();
// Seed story via CRDT (the only source of truth).
crate::db::ensure_content_store();
crate::db::write_item_with_content("10_story_foo", "3_qa", "---\nname: Foo Story\n---\n");
let (watcher_tx, watcher_rx) = broadcast::channel::<WatcherEvent>(16);
let (transport, calls) = MockTransport::new();
@@ -681,38 +665,37 @@ mod tests {
#[test]
fn read_story_name_reads_from_front_matter() {
let tmp = tempfile::tempdir().unwrap();
let stage_dir = tmp.path().join(".huskies").join("work").join("2_current");
std::fs::create_dir_all(&stage_dir).unwrap();
std::fs::write(
stage_dir.join("42_story_my_feature.md"),
crate::db::ensure_content_store();
crate::db::write_item_with_content(
"9942_story_my_feature",
"2_current",
"---\nname: My Cool Feature\n---\n# Story\n",
)
.unwrap();
);
let name = read_story_name(tmp.path(), "2_current", "42_story_my_feature");
let tmp = tempfile::tempdir().unwrap();
let name = read_story_name(tmp.path(), "2_current", "9942_story_my_feature");
assert_eq!(name.as_deref(), Some("My Cool Feature"));
}
#[test]
fn read_story_name_returns_none_for_missing_file() {
crate::db::ensure_content_store();
let tmp = tempfile::tempdir().unwrap();
let name = read_story_name(tmp.path(), "2_current", "99_story_missing");
let name = read_story_name(tmp.path(), "2_current", "99_story_missing_notif_test");
assert_eq!(name, None);
}
#[test]
fn read_story_name_returns_none_for_missing_name_field() {
let tmp = tempfile::tempdir().unwrap();
let stage_dir = tmp.path().join(".huskies").join("work").join("2_current");
std::fs::create_dir_all(&stage_dir).unwrap();
std::fs::write(
stage_dir.join("42_story_no_name.md"),
crate::db::ensure_content_store();
crate::db::write_item_with_content(
"9943_story_no_name",
"2_current",
"---\ncoverage_baseline: 50%\n---\n# Story\n",
)
.unwrap();
);
let name = read_story_name(tmp.path(), "2_current", "42_story_no_name");
let tmp = tempfile::tempdir().unwrap();
let name = read_story_name(tmp.path(), "2_current", "9943_story_no_name");
assert_eq!(name, None);
}
@@ -786,13 +769,13 @@ mod tests {
#[tokio::test]
async fn story_blocked_sends_notification_with_reason() {
let tmp = tempfile::tempdir().unwrap();
let stage_dir = tmp.path().join(".huskies").join("work").join("2_current");
std::fs::create_dir_all(&stage_dir).unwrap();
std::fs::write(
stage_dir.join("425_story_blocking_test.md"),
// Seed story via CRDT (the only source of truth).
crate::db::ensure_content_store();
crate::db::write_item_with_content(
"425_story_blocking_test",
"2_current",
"---\nname: Blocking Test Story\n---\n",
)
.unwrap();
);
let (watcher_tx, watcher_rx) = broadcast::channel::<WatcherEvent>(16);
let (transport, calls) = MockTransport::new();