huskies: merge 1010
This commit is contained in:
@@ -55,8 +55,8 @@ pub use types::{
|
||||
pub use write::{
|
||||
bump_retry_count, migrate_legacy_stage_strings, migrate_merge_job, migrate_names_from_slugs,
|
||||
migrate_node_claims_to_agent_claims, migrate_story_ids_to_numeric, name_from_story_id,
|
||||
set_agent, set_depends_on, set_epic, set_item_type, set_name, set_qa_mode, set_resume_to,
|
||||
set_resume_to_raw, set_retry_count, write_item,
|
||||
set_agent, set_depends_on, set_epic, set_item_type, set_name, set_plan_state, set_qa_mode,
|
||||
set_resume_to, set_resume_to_raw, set_retry_count, write_item,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -110,7 +110,7 @@ pub fn is_claimed_by_us(story_id: &str) -> bool {
|
||||
return false;
|
||||
};
|
||||
let claim = match item.stage() {
|
||||
crate::pipeline_state::Stage::Coding { claim } => claim.as_ref(),
|
||||
crate::pipeline_state::Stage::Coding { claim, .. } => claim.as_ref(),
|
||||
crate::pipeline_state::Stage::Merge { claim, .. } => claim.as_ref(),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
@@ -398,6 +398,11 @@ pub(super) fn extract_item_view(item: &PipelineItemCrdt) -> Option<PipelineItemV
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let plan_state_str = match item.plan_state.view() {
|
||||
JsonValue::String(s) if !s.is_empty() => Some(s),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let stage = project_stage_for_view(
|
||||
&stage_str,
|
||||
&story_id,
|
||||
@@ -405,6 +410,7 @@ pub(super) fn extract_item_view(item: &PipelineItemCrdt) -> Option<PipelineItemV
|
||||
resume_to.as_deref(),
|
||||
claim_agent.as_deref(),
|
||||
claim_ts_secs,
|
||||
plan_state_str.as_deref(),
|
||||
)?;
|
||||
|
||||
Some(PipelineItemView {
|
||||
@@ -440,8 +446,11 @@ fn project_stage_for_view(
|
||||
resume_to: Option<&str>,
|
||||
claim_agent: Option<&str>,
|
||||
claim_ts_secs: Option<u64>,
|
||||
plan_state_str: Option<&str>,
|
||||
) -> Option<crate::pipeline_state::Stage> {
|
||||
use crate::pipeline_state::{AgentClaim, AgentName, ArchiveReason, BranchName, GitSha, Stage};
|
||||
use crate::pipeline_state::{
|
||||
AgentClaim, AgentName, ArchiveReason, BranchName, GitSha, PlanState, Stage,
|
||||
};
|
||||
use chrono::{DateTime, TimeZone, Utc};
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
@@ -470,7 +479,10 @@ fn project_stage_for_view(
|
||||
Box::new(
|
||||
resume_to
|
||||
.and_then(Stage::from_dir)
|
||||
.unwrap_or(Stage::Coding { claim: None }),
|
||||
.unwrap_or(Stage::Coding {
|
||||
claim: None,
|
||||
plan: PlanState::Missing,
|
||||
}),
|
||||
)
|
||||
};
|
||||
|
||||
@@ -489,7 +501,10 @@ fn project_stage_for_view(
|
||||
match clean {
|
||||
"upcoming" => Some(Stage::Upcoming),
|
||||
"backlog" => Some(Stage::Backlog),
|
||||
"coding" => Some(Stage::Coding { claim }),
|
||||
"coding" => Some(Stage::Coding {
|
||||
claim,
|
||||
plan: PlanState::from_str(plan_state_str.unwrap_or("")),
|
||||
}),
|
||||
"qa" => Some(Stage::Qa),
|
||||
"blocked" => Some(Stage::Blocked {
|
||||
reason: String::new(),
|
||||
|
||||
@@ -96,6 +96,10 @@ pub struct PipelineItemCrdt {
|
||||
/// `"rejected"`. These stages never have a resume target, so the
|
||||
/// register is exclusively available for their metadata.
|
||||
pub resume_to: LwwRegisterCrdt<String>,
|
||||
/// Story 1010: lifecycle state of `PLAN.md` in the coding worktree.
|
||||
/// Wire values: `"missing"` (default/empty), `"drafted"`, `"confirmed"`.
|
||||
/// Updated by the filesystem watcher on PLAN.md create/modify/remove events.
|
||||
pub plan_state: LwwRegisterCrdt<String>,
|
||||
}
|
||||
|
||||
/// CRDT node that holds a single peer's presence entry.
|
||||
@@ -518,7 +522,10 @@ mod tests {
|
||||
let evt = CrdtEvent {
|
||||
story_id: "42_story_foo".to_string(),
|
||||
from_stage: Some(crate::pipeline_state::Stage::Backlog),
|
||||
to_stage: crate::pipeline_state::Stage::Coding { claim: None },
|
||||
to_stage: crate::pipeline_state::Stage::Coding {
|
||||
claim: None,
|
||||
plan: crate::pipeline_state::PlanState::Missing,
|
||||
},
|
||||
name: "Foo Feature".to_string(),
|
||||
};
|
||||
assert_eq!(evt.story_id, "42_story_foo");
|
||||
@@ -678,7 +685,10 @@ mod tests {
|
||||
let evt = CrdtEvent {
|
||||
story_id: "70_story_broadcast".to_string(),
|
||||
from_stage: Some(Stage::Backlog),
|
||||
to_stage: Stage::Coding { claim: None },
|
||||
to_stage: Stage::Coding {
|
||||
claim: None,
|
||||
plan: crate::pipeline_state::PlanState::Missing,
|
||||
},
|
||||
name: "Broadcast Test".to_string(),
|
||||
};
|
||||
tx.send(evt).unwrap();
|
||||
|
||||
@@ -211,6 +211,30 @@ pub fn set_qa_mode(story_id: &str, mode: Option<QaMode>) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Set the `plan_state` CRDT register for a pipeline item (story 1010).
|
||||
///
|
||||
/// Encodes the PLAN.md lifecycle as a wire string (`"missing"`, `"drafted"`,
|
||||
/// `"confirmed"`). Called by the filesystem watcher when PLAN.md is created,
|
||||
/// modified, or removed inside a coding worktree.
|
||||
///
|
||||
/// Returns `true` if the item was found and the op was applied, `false` otherwise.
|
||||
pub fn set_plan_state(story_id: &str, state: crate::pipeline_state::PlanState) -> bool {
|
||||
let Some(state_mutex) = get_crdt() else {
|
||||
return false;
|
||||
};
|
||||
let Ok(mut crdt_state) = state_mutex.lock() else {
|
||||
return false;
|
||||
};
|
||||
let Some(&idx) = crdt_state.index.get(story_id) else {
|
||||
return false;
|
||||
};
|
||||
let value = state.as_str().to_string();
|
||||
apply_and_persist(&mut crdt_state, |s| {
|
||||
s.crdt.doc.items[idx].plan_state.set(value)
|
||||
});
|
||||
true
|
||||
}
|
||||
|
||||
/// Write a pipeline item state through CRDT operations.
|
||||
///
|
||||
/// If the item exists, updates its registers. If not, inserts a new item
|
||||
@@ -232,7 +256,7 @@ pub fn write_item(
|
||||
) {
|
||||
let stage_str = stage_dir_name(stage);
|
||||
let claim: Option<&AgentClaim> = match stage {
|
||||
Stage::Coding { claim } => claim.as_ref(),
|
||||
Stage::Coding { claim, .. } => claim.as_ref(),
|
||||
Stage::Merge { claim, .. } => claim.as_ref(),
|
||||
_ => None,
|
||||
};
|
||||
@@ -350,6 +374,7 @@ pub fn write_item(
|
||||
"item_type": "",
|
||||
"epic": "",
|
||||
"resume_to": "",
|
||||
"plan_state": "",
|
||||
})
|
||||
.into();
|
||||
|
||||
@@ -378,6 +403,7 @@ pub fn write_item(
|
||||
item.item_type.advance_seq(floor);
|
||||
item.epic.advance_seq(floor);
|
||||
item.resume_to.advance_seq(floor);
|
||||
item.plan_state.advance_seq(floor);
|
||||
}
|
||||
|
||||
// Broadcast a CrdtEvent for the new item.
|
||||
|
||||
@@ -333,7 +333,7 @@ mod stage_migration_tests {
|
||||
use super::super::item::write_item;
|
||||
use super::*;
|
||||
use crate::crdt_state::read_item;
|
||||
use crate::pipeline_state::{BranchName, Stage};
|
||||
use crate::pipeline_state::{BranchName, PlanState, Stage};
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
/// Seed a pipeline item with a raw, possibly-legacy stage register value,
|
||||
@@ -370,7 +370,10 @@ mod stage_migration_tests {
|
||||
(
|
||||
"9503_legacy_coding",
|
||||
"2_current",
|
||||
Stage::Coding { claim: None },
|
||||
Stage::Coding {
|
||||
claim: None,
|
||||
plan: PlanState::Missing,
|
||||
},
|
||||
),
|
||||
(
|
||||
"9504_legacy_blocked",
|
||||
@@ -452,7 +455,10 @@ mod stage_migration_tests {
|
||||
// Seed two items: one already in clean form, one in legacy form.
|
||||
write_item(
|
||||
"9520_already_clean",
|
||||
&Stage::Coding { claim: None },
|
||||
&Stage::Coding {
|
||||
claim: None,
|
||||
plan: PlanState::Missing,
|
||||
},
|
||||
Some("Already Clean"),
|
||||
None,
|
||||
None,
|
||||
|
||||
@@ -10,8 +10,8 @@ mod migrations;
|
||||
mod tests;
|
||||
|
||||
pub use item::{
|
||||
bump_retry_count, set_agent, set_depends_on, set_epic, set_item_type, set_name, set_qa_mode,
|
||||
set_resume_to, set_resume_to_raw, set_retry_count, write_item,
|
||||
bump_retry_count, set_agent, set_depends_on, set_epic, set_item_type, set_name, set_plan_state,
|
||||
set_qa_mode, set_resume_to, set_resume_to_raw, set_retry_count, write_item,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user