//! Backlog promotion: scan `1_backlog/` and promote stories whose `depends_on` are all met. use std::path::Path; use crate::slog; use crate::slog_warn; use super::super::AgentPool; use super::scan::scan_stage_items; use super::story_checks::{check_archived_dependencies, has_unmet_dependencies}; impl AgentPool { /// Scan `1_backlog/` and promote any story whose `depends_on` are all met. /// /// A story is only promoted if it explicitly lists `depends_on` AND every /// listed dependency has reached `5_done` or `6_archived`. Stories with no /// `depends_on` are left in the backlog for human scheduling. /// /// **Archived dep semantics:** a dep in `6_archived` counts as satisfied (since /// stories auto-sweep from `5_done` to `6_archived` after 4 hours, and the /// dependent story would normally already be promoted by then). However, if a /// dep was already in `6_archived` when the dependent story was created (e.g. it /// was abandoned/superseded before the dependent existed), a prominent warning is /// logged so the user can see the promotion was triggered by an archived dep, not /// a clean completion. pub(super) fn promote_ready_backlog_stories(&self, project_root: &Path) { use crate::db::yaml_legacy::parse_front_matter; let items = scan_stage_items(project_root, "1_backlog"); for story_id in &items { // Only promote stories that explicitly declare dependencies. let contents = crate::db::read_content(story_id); let has_deps = contents .and_then(|c| parse_front_matter(&c).ok()) .and_then(|m| m.depends_on) .map(|d| !d.is_empty()) .unwrap_or(false); if !has_deps { continue; } // Check whether any dependencies are still unmet. if has_unmet_dependencies(project_root, "1_backlog", story_id) { continue; } // Warn if any deps were satisfied via archive rather than via clean done. let archived_deps = check_archived_dependencies(project_root, "1_backlog", story_id); if !archived_deps.is_empty() { slog_warn!( "[auto-assign] Story '{story_id}' is being promoted because deps \ {archived_deps:?} are in 6_archived (not cleanly completed via 5_done). \ These deps may have been abandoned or superseded. If this promotion is \ unintentional, remove the depends_on or manually move the story back to \ 1_backlog." ); } // All deps met — promote from backlog to current. slog!("[auto-assign] Story '{story_id}' deps met; promoting from backlog to current."); if let Err(e) = crate::agents::lifecycle::move_story_to_current(story_id) { slog!("[auto-assign] Failed to promote '{story_id}' to current: {e}"); } } } }