huskies: merge 671_refactor_migrate_pipeline_state_consumers_from_string_comparisons_to_typed_pipelinestage_enum
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
use std::path::Path;
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
use crate::pipeline_state::Stage;
|
||||
use crate::worktree;
|
||||
|
||||
use super::super::super::ReconciliationEvent;
|
||||
@@ -52,20 +53,20 @@ impl AgentPool {
|
||||
let wt_path = wt_entry.path.clone();
|
||||
|
||||
// Determine which active stage the story is in.
|
||||
let stage_dir = match find_active_story_stage(project_root, story_id) {
|
||||
let stage = match find_active_story_stage(project_root, story_id) {
|
||||
Some(s) => s,
|
||||
None => continue, // Not in any active stage (backlog/archived or unknown).
|
||||
};
|
||||
|
||||
// 4_merge/ is left for auto_assign to handle with a fresh mergemaster.
|
||||
if stage_dir == "4_merge" {
|
||||
if matches!(stage, Stage::Merge { .. }) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let _ = progress_tx.send(ReconciliationEvent {
|
||||
story_id: story_id.clone(),
|
||||
status: "checking".to_string(),
|
||||
message: format!("Checking for committed work in {stage_dir}/"),
|
||||
message: format!("Checking for committed work in {}/", stage.dir_name()),
|
||||
});
|
||||
|
||||
// Check whether the worktree has commits ahead of the base branch.
|
||||
@@ -78,7 +79,8 @@ impl AgentPool {
|
||||
|
||||
if !has_work {
|
||||
eprintln!(
|
||||
"[startup:reconcile] No committed work for '{story_id}' in {stage_dir}/; skipping."
|
||||
"[startup:reconcile] No committed work for '{story_id}' in {}/; skipping.",
|
||||
stage.dir_name()
|
||||
);
|
||||
let _ = progress_tx.send(ReconciliationEvent {
|
||||
story_id: story_id.clone(),
|
||||
@@ -89,7 +91,8 @@ impl AgentPool {
|
||||
}
|
||||
|
||||
eprintln!(
|
||||
"[startup:reconcile] Found committed work for '{story_id}' in {stage_dir}/. Running acceptance gates."
|
||||
"[startup:reconcile] Found committed work for '{story_id}' in {}/. Running acceptance gates.",
|
||||
stage.dir_name()
|
||||
);
|
||||
let _ = progress_tx.send(ReconciliationEvent {
|
||||
story_id: story_id.clone(),
|
||||
@@ -130,7 +133,8 @@ impl AgentPool {
|
||||
if !gates_passed {
|
||||
eprintln!(
|
||||
"[startup:reconcile] Gates failed for '{story_id}': {gate_output}\n\
|
||||
Leaving in {stage_dir}/ for auto-assign to restart the agent."
|
||||
Leaving in {}/ for auto-assign to restart the agent.",
|
||||
stage.dir_name()
|
||||
);
|
||||
let _ = progress_tx.send(ReconciliationEvent {
|
||||
story_id: story_id.clone(),
|
||||
@@ -140,9 +144,12 @@ impl AgentPool {
|
||||
continue;
|
||||
}
|
||||
|
||||
eprintln!("[startup:reconcile] Gates passed for '{story_id}' (stage: {stage_dir}/).");
|
||||
eprintln!(
|
||||
"[startup:reconcile] Gates passed for '{story_id}' (stage: {}/).",
|
||||
stage.dir_name()
|
||||
);
|
||||
|
||||
if stage_dir == "2_current" {
|
||||
if matches!(stage, Stage::Coding) {
|
||||
// Coder stage — determine qa mode to decide next step.
|
||||
let qa_mode = {
|
||||
let item_type = crate::agents::lifecycle::item_type_from_id(story_id);
|
||||
@@ -232,7 +239,7 @@ impl AgentPool {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if stage_dir == "3_qa" {
|
||||
} else if matches!(stage, Stage::Qa) {
|
||||
// QA stage → run coverage gate before advancing to merge.
|
||||
let wt_path_for_cov = wt_path.clone();
|
||||
let coverage_result = tokio::task::spawn_blocking(move || {
|
||||
|
||||
@@ -342,17 +342,21 @@ impl AgentPool {
|
||||
// has already reached done or archived (e.g. a previous mergemaster
|
||||
// succeeded), this advance is a zombie — skip it entirely to avoid
|
||||
// phantom notifications and redundant post-merge test runs.
|
||||
if let Ok(Some(typed_item)) = crate::pipeline_state::read_typed(story_id) {
|
||||
if let Ok(Some(typed_item)) = crate::pipeline_state::read_typed(story_id)
|
||||
&& matches!(
|
||||
typed_item.stage,
|
||||
crate::pipeline_state::Stage::Done { .. }
|
||||
| crate::pipeline_state::Stage::Archived { .. }
|
||||
)
|
||||
{
|
||||
let current_dir = typed_item.stage.dir_name();
|
||||
if current_dir == "5_done" || current_dir == "6_archived" {
|
||||
slog!(
|
||||
"[pipeline] Skipping stale mergemaster advance for '{story_id}': \
|
||||
story is already in work/{current_dir}/"
|
||||
);
|
||||
// Skip pipeline advancement — do not run post-merge tests,
|
||||
// do not emit notifications, do not restart agents.
|
||||
return;
|
||||
}
|
||||
slog!(
|
||||
"[pipeline] Skipping stale mergemaster advance for '{story_id}': \
|
||||
story is already in work/{current_dir}/"
|
||||
);
|
||||
// Skip pipeline advancement — do not run post-merge tests,
|
||||
// do not emit notifications, do not restart agents.
|
||||
return;
|
||||
}
|
||||
|
||||
// Block advancement if the mergemaster explicitly reported a failure.
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use std::path::Path;
|
||||
|
||||
use crate::config::ProjectConfig;
|
||||
use crate::pipeline_state::Stage;
|
||||
|
||||
use super::super::super::{PipelineStage, agent_config_stage, pipeline_stage};
|
||||
use super::super::worktree::find_active_story_stage;
|
||||
@@ -30,19 +31,20 @@ pub(super) fn validate_agent_stage(
|
||||
if agent_stage == PipelineStage::Other {
|
||||
return Ok(());
|
||||
}
|
||||
let Some(story_stage_dir) = find_active_story_stage(project_root, story_id) else {
|
||||
let Some(story_stage) = find_active_story_stage(project_root, story_id) else {
|
||||
return Ok(());
|
||||
};
|
||||
let expected_stage = match story_stage_dir {
|
||||
"2_current" => PipelineStage::Coder,
|
||||
"3_qa" => PipelineStage::Qa,
|
||||
"4_merge" => PipelineStage::Mergemaster,
|
||||
let expected_stage = match story_stage {
|
||||
Stage::Coding => PipelineStage::Coder,
|
||||
Stage::Qa => PipelineStage::Qa,
|
||||
Stage::Merge { .. } => PipelineStage::Mergemaster,
|
||||
_ => PipelineStage::Other,
|
||||
};
|
||||
if expected_stage != PipelineStage::Other && expected_stage != agent_stage {
|
||||
return Err(format!(
|
||||
"Agent '{name}' (stage: {agent_stage:?}) cannot be assigned to \
|
||||
story '{story_id}' in {story_stage_dir}/ (requires stage: {expected_stage:?})"
|
||||
story '{story_id}' in {}/ (requires stage: {expected_stage:?})",
|
||||
story_stage.dir_name()
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -21,16 +21,16 @@ impl AgentPool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the active pipeline stage directory name for `story_id`, or `None` if the
|
||||
/// story is not in any active stage (`2_current/`, `3_qa/`, `4_merge/`).
|
||||
/// Return the active pipeline stage for `story_id`, or `None` if the story is not
|
||||
/// in any active stage (`2_current/`, `3_qa/`, `4_merge/`).
|
||||
pub(super) fn find_active_story_stage(
|
||||
_project_root: &Path,
|
||||
story_id: &str,
|
||||
) -> Option<&'static str> {
|
||||
) -> Option<crate::pipeline_state::Stage> {
|
||||
if let Ok(Some(item)) = crate::pipeline_state::read_typed(story_id)
|
||||
&& item.stage.is_active()
|
||||
{
|
||||
return Some(item.stage.dir_name());
|
||||
return Some(item.stage);
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -44,10 +44,10 @@ mod tests {
|
||||
crate::db::ensure_content_store();
|
||||
crate::db::write_item_with_content("10_story_test", "2_current", "---\nname: Test\n---\n");
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
assert_eq!(
|
||||
assert!(matches!(
|
||||
find_active_story_stage(tmp.path(), "10_story_test"),
|
||||
Some("2_current")
|
||||
);
|
||||
Some(crate::pipeline_state::Stage::Coding)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -55,10 +55,10 @@ mod tests {
|
||||
crate::db::ensure_content_store();
|
||||
crate::db::write_item_with_content("11_story_test", "3_qa", "---\nname: Test\n---\n");
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
assert_eq!(
|
||||
assert!(matches!(
|
||||
find_active_story_stage(tmp.path(), "11_story_test"),
|
||||
Some("3_qa")
|
||||
);
|
||||
Some(crate::pipeline_state::Stage::Qa)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -66,10 +66,10 @@ mod tests {
|
||||
crate::db::ensure_content_store();
|
||||
crate::db::write_item_with_content("12_story_test", "4_merge", "---\nname: Test\n---\n");
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
assert_eq!(
|
||||
assert!(matches!(
|
||||
find_active_story_stage(tmp.path(), "12_story_test"),
|
||||
Some("4_merge")
|
||||
);
|
||||
Some(crate::pipeline_state::Stage::Merge { .. })
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user