huskies: merge 1031

This commit is contained in:
dave
2026-05-14 14:31:13 +00:00
parent 3d741acefb
commit bc99821274
4 changed files with 323 additions and 32 deletions
+7 -11
View File
@@ -15,6 +15,7 @@ use crate::http::workflow::{
use crate::io::story_metadata::parse_unchecked_todos;
use crate::service::story::parse_test_cases;
use crate::slog_warn;
use crate::validation::{AddCriterionRequest, EditCriterionRequest};
#[allow(unused_imports)]
use crate::workflow::{
TestCaseResult, TestStatus, WorkflowState, evaluate_acceptance_with_coverage,
@@ -268,13 +269,10 @@ pub(crate) fn tool_edit_criterion(args: &Value, ctx: &AppContext) -> Result<Stri
.get("criterion_index")
.and_then(|v| v.as_u64())
.ok_or("Missing required argument: criterion_index")? as usize;
let new_text = args
.get("new_text")
.and_then(|v| v.as_str())
.ok_or("Missing required argument: new_text")?;
let req = EditCriterionRequest::from_json(args)?;
let root = ctx.state.get_project_root()?;
edit_criterion_in_file(&root, story_id, criterion_index, new_text)?;
edit_criterion_in_file(&root, story_id, criterion_index, req.new_text.as_ref())?;
Ok(format!(
"Criterion {criterion_index} updated for story '{story_id}'."
@@ -286,16 +284,14 @@ pub(crate) fn tool_add_criterion(args: &Value, ctx: &AppContext) -> Result<Strin
.get("story_id")
.and_then(|v| v.as_str())
.ok_or("Missing required argument: story_id")?;
let criterion = args
.get("criterion")
.and_then(|v| v.as_str())
.ok_or("Missing required argument: criterion")?;
let req = AddCriterionRequest::from_json(args)?;
let root = ctx.state.get_project_root()?;
add_criterion_to_file(&root, story_id, criterion)?;
add_criterion_to_file(&root, story_id, req.criterion.as_ref())?;
Ok(format!(
"Added criterion to story '{story_id}': - [ ] {criterion}"
"Added criterion to story '{story_id}': - [ ] {}",
req.criterion.as_ref()
))
}
+21 -19
View File
@@ -3,6 +3,7 @@
use crate::http::context::AppContext;
use crate::http::workflow::update_story_in_file;
use crate::slog_warn;
use crate::validation::UpdateStoryRequest;
use serde_json::Value;
pub(crate) fn tool_update_story(args: &Value, ctx: &AppContext) -> Result<String, String> {
@@ -10,23 +11,20 @@ pub(crate) fn tool_update_story(args: &Value, ctx: &AppContext) -> Result<String
.get("story_id")
.and_then(|v| v.as_str())
.ok_or("Missing required argument: story_id")?;
let user_story = args.get("user_story").and_then(|v| v.as_str());
let description = args.get("description").and_then(|v| v.as_str());
let req = UpdateStoryRequest::from_json(args)?;
// Explicit top-level args map onto typed CRDT registers directly (story 929:
// no YAML front-matter writes). The `front_matter` object is the legacy
// 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()) {
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(ref name) = req.name
&& !crate::crdt_state::set_name(story_id, Some(name.as_ref()))
{
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());
@@ -46,11 +44,10 @@ pub(crate) fn tool_update_story(args: &Value, ctx: &AppContext) -> Result<String
);
}
"name" => {
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) {
let raw = value.as_str().unwrap_or("");
let validated = crate::validation::StoryName::parse(raw)
.map_err(|errs| crate::validation::format_errors_as_json(&errs))?;
if !crate::crdt_state::set_name(story_id, Some(validated.as_ref())) {
return Err(format!(
"Story '{story_id}' not found in CRDT — name was not updated."
));
@@ -188,8 +185,13 @@ pub(crate) fn tool_update_story(args: &Value, ctx: &AppContext) -> Result<String
let root = ctx.state.get_project_root()?;
// Only call update_story_in_file when there is body content to update.
if user_story.is_some() || description.is_some() {
update_story_in_file(&root, story_id, user_story, description)?;
if req.user_story.is_some() || req.description.is_some() {
update_story_in_file(
&root,
story_id,
req.user_story.as_ref().map(|d| d.as_str()),
req.description.as_ref().map(|d| d.as_str()),
)?;
}
// Bug 503: warn if any depends_on in the (now updated) story points at an archived story.