huskies: merge 964
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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(),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user