huskies: merge 556_bug_stale_filesystem_shadows_in_1_backlog_cause_auto_assign_to_promote_archived_stories

This commit is contained in:
dave
2026-04-13 14:44:40 +00:00
parent 845b85e7a7
commit d618bc3b32
2 changed files with 47 additions and 1 deletions
@@ -30,6 +30,9 @@ pub(super) fn scan_stage_items(project_root: &Path, stage_dir: &str) -> Vec<Stri
}
// Also include filesystem items (backwards compat / migration fallback).
// Skip any story the CRDT already tracks — its authoritative stage wins.
// Stale .md files in pipeline dirs (e.g. 1_backlog/) must not shadow an
// archived or otherwise-moved story in the CRDT.
let dir = project_root.join(".huskies").join("work").join(stage_dir);
if dir.is_dir()
&& let Ok(entries) = std::fs::read_dir(&dir)
@@ -39,6 +42,14 @@ pub(super) fn scan_stage_items(project_root: &Path, stage_dir: &str) -> Vec<Stri
if path.extension().and_then(|e| e.to_str()) == Some("md")
&& let Some(stem) = path.file_stem().and_then(|s| s.to_str())
{
// If the CRDT knows about this story (any stage), trust the CRDT.
if crate::pipeline_state::read_typed(stem)
.ok()
.flatten()
.is_some()
{
continue;
}
items.insert(stem.to_string());
}
}
@@ -167,6 +178,39 @@ mod tests {
}
}
// ── Bug 556: stale filesystem shadow must not override CRDT stage ──────────
//
// A story file left in 1_backlog/ on disk but tracked as 6_archived in the
// CRDT must NOT appear when scanning 1_backlog. Without the fix, the
// filesystem fallback would add it, causing promote_ready_backlog_stories to
// attempt to promote an archived story.
#[test]
fn scan_stage_items_skips_filesystem_item_known_to_crdt_at_different_stage() {
crate::db::ensure_content_store();
// Write the story into the CRDT as 6_archived.
crate::db::write_item_with_content(
"9970_story_archived",
"6_archived",
"---\nname: Archived\n---\n",
);
// Also place a stale .md file in a temp 1_backlog/ dir.
let tmp = tempfile::tempdir().unwrap();
let backlog = tmp.path().join(".huskies/work/1_backlog");
std::fs::create_dir_all(&backlog).unwrap();
std::fs::write(
backlog.join("9970_story_archived.md"),
"---\nname: Archived\n---\n",
)
.unwrap();
let items = scan_stage_items(tmp.path(), "1_backlog");
assert!(
!items.contains(&"9970_story_archived".to_string()),
"archived CRDT story must not appear in 1_backlog scan via stale filesystem shadow"
);
}
#[test]
fn scan_stage_items_returns_empty_for_missing_dir() {
// Use a unique stage name that no other test writes to, so
+3 -1
View File
@@ -67,7 +67,9 @@ fn read_coverage_report(path: &std::path::Path) -> String {
let report: CoverageReport = match serde_json::from_str(&content) {
Ok(r) => r,
Err(e) => {
return format!("**Coverage (cached)**\n\nFailed to parse `.coverage_report.json`: {e}");
return format!(
"**Coverage (cached)**\n\nFailed to parse `.coverage_report.json`: {e}"
);
}
};