huskies: merge 503_bug_depends_on_pointing_at_an_archived_story_is_silently_treated_as_deps_met_surprising_users

This commit is contained in:
dave
2026-04-09 18:27:25 +00:00
parent 8b2e068d3e
commit 41515e3b8f
6 changed files with 393 additions and 3 deletions
@@ -84,6 +84,25 @@ pub(super) fn has_unmet_dependencies(
!crate::io::story_metadata::check_unmet_deps(project_root, stage_dir, story_id).is_empty()
}
/// Return the list of dependency story numbers that are in `6_archived` (satisfied
/// via archive rather than via a clean `5_done` completion).
///
/// Used to emit a warning when backlog promotion fires because one or more deps were
/// archived. Returns an empty `Vec` when no deps are archived. Reads from CRDT
/// first; falls back to filesystem when CRDT is not initialised.
pub(super) fn check_archived_dependencies(
project_root: &Path,
stage_dir: &str,
story_id: &str,
) -> Vec<u32> {
// Prefer CRDT-based check when the item is known to CRDT.
if crate::crdt_state::read_item(story_id).is_some() {
return crate::crdt_state::check_archived_deps_crdt(story_id);
}
// Fallback: filesystem.
crate::io::story_metadata::check_archived_deps(project_root, stage_dir, story_id)
}
/// Return `true` if the story file has a `merge_failure` field in its front matter.
pub(super) fn has_merge_failure(project_root: &Path, _stage_dir: &str, story_id: &str) -> bool {
use crate::io::story_metadata::parse_front_matter;
@@ -170,4 +189,44 @@ mod tests {
std::fs::write(current.join("5_story_free.md"), "---\nname: Free\n---\n").unwrap();
assert!(!has_unmet_dependencies(tmp.path(), "2_current", "5_story_free"));
}
// ── Bug 503: archived-dep visibility ─────────────────────────────────────
/// check_archived_dependencies returns dep IDs that are in 6_archived.
#[test]
fn check_archived_dependencies_returns_archived_ids() {
let tmp = tempfile::tempdir().unwrap();
let backlog = tmp.path().join(".huskies/work/1_backlog");
let archived = tmp.path().join(".huskies/work/6_archived");
std::fs::create_dir_all(&backlog).unwrap();
std::fs::create_dir_all(&archived).unwrap();
std::fs::write(archived.join("500_spike_crdt.md"), "---\nname: CRDT Spike\n---\n").unwrap();
std::fs::write(
backlog.join("503_story_dependent.md"),
"---\nname: Dependent\ndepends_on: [500]\n---\n",
)
.unwrap();
let archived_deps =
check_archived_dependencies(tmp.path(), "1_backlog", "503_story_dependent");
assert_eq!(archived_deps, vec![500]);
}
/// check_archived_dependencies returns empty when dep is in 5_done (not archived).
#[test]
fn check_archived_dependencies_empty_when_dep_in_done() {
let tmp = tempfile::tempdir().unwrap();
let backlog = tmp.path().join(".huskies/work/1_backlog");
let done = tmp.path().join(".huskies/work/5_done");
std::fs::create_dir_all(&backlog).unwrap();
std::fs::create_dir_all(&done).unwrap();
std::fs::write(done.join("490_story_done.md"), "---\nname: Done\n---\n").unwrap();
std::fs::write(
backlog.join("503_story_waiting.md"),
"---\nname: Waiting\ndepends_on: [490]\n---\n",
)
.unwrap();
let archived_deps =
check_archived_dependencies(tmp.path(), "1_backlog", "503_story_waiting");
assert!(archived_deps.is_empty());
}
}