2026-04-29 09:49:45 +00:00
|
|
|
//! 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) {
|
2026-05-08 14:24:20 +00:00
|
|
|
use crate::db::yaml_legacy::parse_front_matter;
|
2026-04-29 09:49:45 +00:00
|
|
|
|
|
|
|
|
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}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|