huskies: merge 964

This commit is contained in:
dave
2026-05-13 14:51:39 +00:00
parent c811672e18
commit dcb43c465a
24 changed files with 234 additions and 188 deletions
+3 -1
View File
@@ -37,7 +37,9 @@ pub(crate) fn tool_get_story_todos(args: &Value, ctx: &AppContext) -> Result<Str
let contents = crate::http::workflow::read_story_content(&root, story_id)
.map_err(|_| format!("Story file not found: {story_id}.md"))?;
let story_name = crate::crdt_state::read_item(story_id).map(|v| v.name().to_string());
let story_name = crate::crdt_state::read_item(story_id)
.map(|v| v.name().to_string())
.unwrap_or_default();
let todos = parse_unchecked_todos(&contents);
serde_json::to_string_pretty(&json!({
@@ -18,7 +18,15 @@ pub(crate) fn tool_update_story(args: &Value, ctx: &AppContext) -> Result<String
// escape hatch; every known key is recognised and routed below, and any
// unknown key is rejected loudly rather than silently flushed to disk.
if let Some(name) = args.get("name").and_then(|v| v.as_str()) {
crate::crdt_state::set_name(story_id, Some(name));
if name.trim().is_empty() {
return Err("name must not be empty".to_string());
}
if !crate::crdt_state::set_name(story_id, Some(name)) {
return Err(format!(
"Story '{story_id}' not found in CRDT — name was not updated. \
The story may not exist or may not yet be registered."
));
}
}
if let Some(agent) = args.get("agent").and_then(|v| v.as_str()) {
crate::crdt_state::set_agent(story_id, agent.parse::<crate::config::AgentName>().ok());
@@ -38,8 +46,15 @@ pub(crate) fn tool_update_story(args: &Value, ctx: &AppContext) -> Result<String
);
}
"name" => {
let s = value.as_str().filter(|s| !s.is_empty());
crate::crdt_state::set_name(story_id, s);
let s = value.as_str().filter(|s| !s.trim().is_empty());
if s.is_none() {
return Err("name must not be empty".to_string());
}
if !crate::crdt_state::set_name(story_id, s) {
return Err(format!(
"Story '{story_id}' not found in CRDT — name was not updated."
));
}
}
"agent" => {
let parsed = value
+5 -13
View File
@@ -18,7 +18,7 @@ pub struct AgentAssignment {
#[derive(Clone, Debug, Serialize)]
pub struct UpcomingStory {
pub story_id: String,
pub name: Option<String>,
pub name: String,
pub error: Option<String>,
/// Merge failure reason persisted to front matter by the mergemaster agent.
pub merge_failure: Option<String>,
@@ -123,11 +123,7 @@ pub fn load_pipeline_state(ctx: &AppContext) -> Result<PipelineState, String> {
let story = UpcomingStory {
story_id: sid.clone(),
name: if item.name.is_empty() {
None
} else {
Some(item.name.clone())
},
name: item.name.clone(),
error: None,
merge_failure,
agent,
@@ -248,11 +244,7 @@ pub fn load_upcoming_stories(_ctx: &AppContext) -> Result<Vec<UpcomingStory>, St
let epic_id = crate::crdt_state::read_item(sid).and_then(|v| v.epic());
UpcomingStory {
story_id: item.story_id.0.clone(),
name: if item.name.is_empty() {
None
} else {
Some(item.name)
},
name: item.name,
error: None,
merge_failure: None,
agent: None,
@@ -546,12 +538,12 @@ mod tests {
.iter()
.find(|s| s.story_id == "9870_story_view_upcoming")
.unwrap();
assert_eq!(s1.name.as_deref(), Some("View Upcoming"));
assert_eq!(s1.name, "View Upcoming");
let s2 = stories
.iter()
.find(|s| s.story_id == "9871_story_worktree")
.unwrap();
assert_eq!(s2.name.as_deref(), Some("Worktree Orchestration"));
assert_eq!(s2.name, "Worktree Orchestration");
}
#[test]
+3 -3
View File
@@ -363,7 +363,7 @@ async fn ws_handler_forwards_status_events_as_status_update() {
// Use a story ID unique enough that genuine server logs won't match it.
ctx.services.status.publish(StatusEvent::StageTransition {
story_id: "77_story_status_fwd_test".to_string(),
story_name: Some("StatusFwdTest".to_string()),
story_name: "StatusFwdTest".to_string(),
from_stage: "1_backlog".to_string(),
to_stage: "2_current".to_string(),
});
@@ -396,7 +396,7 @@ async fn ws_handler_multi_project_status_isolation() {
let needle = "ProjAIsolation7734";
ctx_a.services.status.publish(StatusEvent::MergeFailure {
story_id: "10_story_proj_a_isolation".to_string(),
story_name: Some(needle.to_string()),
story_name: needle.to_string(),
reason: "conflict".to_string(),
});
@@ -453,7 +453,7 @@ async fn ws_handler_status_consumer_disabled_via_config() {
let needle = "DisabledConsumer9182";
ctx.services.status.publish(StatusEvent::StoryBlocked {
story_id: "55_story_disabled_consumer".to_string(),
story_name: Some(needle.to_string()),
story_name: needle.to_string(),
reason: "test".to_string(),
});