huskies: merge 959
This commit is contained in:
@@ -385,8 +385,11 @@ pub fn move_story_to_stage(story_id: &str, target_stage: &str) -> Result<(String
|
|||||||
let item = read_typed_or_err(story_id)?;
|
let item = read_typed_or_err(story_id)?;
|
||||||
let from_name = stage_to_name(&item.stage);
|
let from_name = stage_to_name(&item.stage);
|
||||||
|
|
||||||
// Idempotent: already in the target stage.
|
// Idempotent: already in the target stage. Compare via Stage discriminant
|
||||||
if item.stage.dir_name() == target_wire {
|
// so the check is typed rather than a raw string equality.
|
||||||
|
let already_there = Stage::from_dir(target_wire)
|
||||||
|
.is_some_and(|t| std::mem::discriminant(&item.stage) == std::mem::discriminant(&t));
|
||||||
|
if already_there {
|
||||||
return Ok((target_stage.to_string(), target_stage.to_string()));
|
return Ok((target_stage.to_string(), target_stage.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,11 +40,10 @@ pub(super) fn scan_stage_items(_project_root: &Path, stage_dir: &str) -> Vec<Str
|
|||||||
let Some(want) = crate::pipeline_state::Stage::from_dir(normalised) else {
|
let Some(want) = crate::pipeline_state::Stage::from_dir(normalised) else {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
};
|
};
|
||||||
let want = want.dir_name();
|
|
||||||
|
|
||||||
// CRDT is the only source of truth — no filesystem fallback.
|
// CRDT is the only source of truth — no filesystem fallback.
|
||||||
for item in crate::pipeline_state::read_all_typed() {
|
for item in crate::pipeline_state::read_all_typed() {
|
||||||
if item.stage.dir_name() == want {
|
if std::mem::discriminant(&item.stage) == std::mem::discriminant(&want) {
|
||||||
items.insert(item.story_id.0.clone());
|
items.insert(item.story_id.0.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,19 +134,7 @@ pub(crate) fn tool_show_epic(args: &Value, _ctx: &AppContext) -> Result<String,
|
|||||||
if member_view.epic() == epic_numeric {
|
if member_view.epic() == epic_numeric {
|
||||||
// Story 945: Frozen / ReviewHold / MergeFailureFinal are first-class
|
// Story 945: Frozen / ReviewHold / MergeFailureFinal are first-class
|
||||||
// Stage variants — no more orthogonal boolean flags.
|
// Stage variants — no more orthogonal boolean flags.
|
||||||
let stage_name = match &item.stage {
|
let stage_name = item.stage.dir_name();
|
||||||
Stage::Upcoming | Stage::Backlog => "backlog",
|
|
||||||
Stage::Coding => "current",
|
|
||||||
Stage::Qa => "qa",
|
|
||||||
Stage::Merge { .. } => "merge",
|
|
||||||
Stage::Done { .. } => "done",
|
|
||||||
Stage::Archived { .. } => "archived",
|
|
||||||
Stage::MergeFailure { .. } => "merge_failure",
|
|
||||||
Stage::MergeFailureFinal { .. } => "merge_failure_final",
|
|
||||||
Stage::Blocked { .. } => "blocked",
|
|
||||||
Stage::Frozen { .. } => "frozen",
|
|
||||||
Stage::ReviewHold { .. } => "review_hold",
|
|
||||||
};
|
|
||||||
if matches!(item.stage, Stage::Done { .. }) {
|
if matches!(item.stage, Stage::Done { .. }) {
|
||||||
done += 1;
|
done += 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,21 +66,14 @@ pub struct PipelineState {
|
|||||||
pub deterministic_merges_in_flight: Vec<String>,
|
pub deterministic_merges_in_flight: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine which pipeline bucket a frozen item's `resume_to` stage maps to.
|
/// Unwrap nested `Stage::Frozen` layers to find the innermost resume target.
|
||||||
///
|
///
|
||||||
/// Mirrors the routing in `load_pipeline_state` for non-frozen items so that
|
/// Mirrors the routing in `load_pipeline_state` for non-frozen items so that
|
||||||
/// a frozen story always appears under the same section it was in before freezing.
|
/// a frozen story always appears under the same section it was in before freezing.
|
||||||
fn frozen_resume_bucket(resume_to: &crate::pipeline_state::Stage) -> &'static str {
|
fn unwrap_frozen(stage: &crate::pipeline_state::Stage) -> &crate::pipeline_state::Stage {
|
||||||
use crate::pipeline_state::Stage;
|
match stage {
|
||||||
match resume_to {
|
crate::pipeline_state::Stage::Frozen { resume_to: inner } => unwrap_frozen(inner),
|
||||||
Stage::Upcoming | Stage::Backlog => "backlog",
|
other => other,
|
||||||
Stage::Coding | Stage::Blocked { .. } => "current",
|
|
||||||
Stage::Qa | Stage::ReviewHold { .. } => "qa",
|
|
||||||
Stage::Merge { .. } | Stage::MergeFailure { .. } | Stage::MergeFailureFinal { .. } => {
|
|
||||||
"merge"
|
|
||||||
}
|
|
||||||
Stage::Frozen { resume_to: inner } => frozen_resume_bucket(inner),
|
|
||||||
_ => "backlog", // Done, Archived → fall back to backlog (should not occur)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,10 +175,12 @@ pub fn load_pipeline_state(ctx: &AppContext) -> Result<PipelineState, String> {
|
|||||||
Stage::Frozen { resume_to } => {
|
Stage::Frozen { resume_to } => {
|
||||||
// Route to the section matching the stage that was active when
|
// Route to the section matching the stage that was active when
|
||||||
// the item was frozen, so it appears in-place.
|
// the item was frozen, so it appears in-place.
|
||||||
match frozen_resume_bucket(resume_to) {
|
match unwrap_frozen(resume_to) {
|
||||||
"current" => state.current.push(story),
|
Stage::Coding | Stage::Blocked { .. } => state.current.push(story),
|
||||||
"qa" => state.qa.push(story),
|
Stage::Qa | Stage::ReviewHold { .. } => state.qa.push(story),
|
||||||
"merge" => state.merge.push(story),
|
Stage::Merge { .. }
|
||||||
|
| Stage::MergeFailure { .. }
|
||||||
|
| Stage::MergeFailureFinal { .. } => state.merge.push(story),
|
||||||
_ => state.backlog.push(story),
|
_ => state.backlog.push(story),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user