huskies: merge 880

This commit is contained in:
dave
2026-04-29 21:41:44 +00:00
parent 4d24b5b661
commit 7e2f122d36
16 changed files with 508 additions and 40 deletions
+47 -35
View File
@@ -40,6 +40,9 @@ pub struct UpcomingStory {
/// Story numbers this story depends on.
#[serde(skip_serializing_if = "Option::is_none")]
pub depends_on: Option<Vec<u32>>,
/// Epic this item belongs to (numeric ID as string, e.g. "880").
#[serde(skip_serializing_if = "Option::is_none")]
pub epic_id: Option<String>,
}
/// Validation outcome for a single story.
@@ -92,17 +95,18 @@ pub fn load_pipeline_state(ctx: &AppContext) -> Result<PipelineState, String> {
let sid = &item.story_id.0;
let agent = agent_map.get(sid).cloned();
// Enrich with content-derived metadata (merge_failure, review_hold, qa).
let (merge_failure, review_hold, qa) = crate::db::read_content(sid)
// Enrich with content-derived metadata (merge_failure, review_hold, qa, epic_id).
let (merge_failure, review_hold, qa, epic_id) = crate::db::read_content(sid)
.and_then(|c| parse_front_matter(&c).ok())
.map(|meta| {
(
meta.merge_failure,
meta.review_hold,
meta.qa.map(|m| m.as_str().to_string()),
meta.epic,
)
})
.unwrap_or((None, None, None));
.unwrap_or((None, None, None, None));
let story = UpcomingStory {
story_id: sid.clone(),
@@ -136,6 +140,7 @@ pub fn load_pipeline_state(ctx: &AppContext) -> Result<PipelineState, String> {
.collect(),
)
},
epic_id,
};
match &item.stage {
Stage::Upcoming => state.backlog.push(story), // upcoming shown with backlog
@@ -201,38 +206,45 @@ pub fn load_upcoming_stories(_ctx: &AppContext) -> Result<Vec<UpcomingStory>, St
let mut stories: Vec<UpcomingStory> = typed_items
.into_iter()
.filter(|item| matches!(item.stage, Stage::Backlog))
.map(|item| UpcomingStory {
story_id: item.story_id.0,
name: if item.name.is_empty() {
None
} else {
Some(item.name)
},
error: None,
merge_failure: None,
agent: None,
review_hold: None,
qa: None,
retry_count: if item.retry_count > 0 {
Some(item.retry_count)
} else {
None
},
blocked: if item.stage.is_blocked() {
Some(true)
} else {
None
},
depends_on: if item.depends_on.is_empty() {
None
} else {
Some(
item.depends_on
.iter()
.filter_map(|d| d.0.split('_').next()?.parse::<u32>().ok())
.collect(),
)
},
.map(|item| {
let sid = &item.story_id.0;
let epic_id = crate::db::read_content(sid)
.and_then(|c| parse_front_matter(&c).ok())
.and_then(|meta| meta.epic);
UpcomingStory {
story_id: item.story_id.0.clone(),
name: if item.name.is_empty() {
None
} else {
Some(item.name)
},
error: None,
merge_failure: None,
agent: None,
review_hold: None,
qa: None,
retry_count: if item.retry_count > 0 {
Some(item.retry_count)
} else {
None
},
blocked: if item.stage.is_blocked() {
Some(true)
} else {
None
},
depends_on: if item.depends_on.is_empty() {
None
} else {
Some(
item.depends_on
.iter()
.filter_map(|d| d.0.split('_').next()?.parse::<u32>().ok())
.collect(),
)
},
epic_id,
}
})
.collect();
stories.sort_by(|a, b| a.story_id.cmp(&b.story_id));