storkit: merge 377_bug_update_story_mcp_tool_writes_front_matter_values_as_yaml_strings_instead_of_native_types

This commit is contained in:
dave
2026-03-23 18:43:14 +00:00
parent 1a7b6c7342
commit 8b85ca743e
2 changed files with 85 additions and 6 deletions

View File

@@ -183,6 +183,18 @@ pub fn add_criterion_to_file(
Ok(())
}
/// Encode a string value as a YAML scalar.
///
/// Booleans (`true`/`false`) and integers are written as native YAML types (unquoted).
/// Everything else is written as a quoted string to avoid ambiguity.
fn yaml_encode_scalar(value: &str) -> String {
match value {
"true" | "false" => value.to_string(),
s if s.parse::<i64>().is_ok() => s.to_string(),
s => format!("\"{}\"", s.replace('"', "\\\"").replace('\n', " ").replace('\r', "")),
}
}
/// Update the user story text and/or description in a story file.
///
/// At least one of `user_story` or `description` must be provided.
@@ -209,7 +221,7 @@ pub fn update_story_in_file(
if let Some(fields) = front_matter {
for (key, value) in fields {
let yaml_value = format!("\"{}\"", value.replace('"', "\\\"").replace('\n', " ").replace('\r', ""));
let yaml_value = yaml_encode_scalar(value);
contents = set_front_matter_field(&contents, key, &yaml_value);
}
}
@@ -589,4 +601,55 @@ mod tests {
let contents = fs::read_to_string(&filepath).unwrap();
assert!(contents.contains("agent: \"dev\""));
}
#[test]
fn update_story_bool_front_matter_written_unquoted() {
let tmp = tempfile::tempdir().unwrap();
let current = tmp.path().join(".storkit/work/2_current");
fs::create_dir_all(&current).unwrap();
let filepath = current.join("27_test.md");
fs::write(&filepath, "---\nname: T\n---\n\nNo sections.\n").unwrap();
let mut fields = HashMap::new();
fields.insert("blocked".to_string(), "false".to_string());
update_story_in_file(tmp.path(), "27_test", None, None, Some(&fields)).unwrap();
let result = fs::read_to_string(&filepath).unwrap();
assert!(result.contains("blocked: false"), "bool should be unquoted: {result}");
assert!(!result.contains("blocked: \"false\""), "bool must not be quoted: {result}");
}
#[test]
fn update_story_integer_front_matter_written_unquoted() {
let tmp = tempfile::tempdir().unwrap();
let current = tmp.path().join(".storkit/work/2_current");
fs::create_dir_all(&current).unwrap();
let filepath = current.join("28_test.md");
fs::write(&filepath, "---\nname: T\n---\n\nNo sections.\n").unwrap();
let mut fields = HashMap::new();
fields.insert("retry_count".to_string(), "0".to_string());
update_story_in_file(tmp.path(), "28_test", None, None, Some(&fields)).unwrap();
let result = fs::read_to_string(&filepath).unwrap();
assert!(result.contains("retry_count: 0"), "integer should be unquoted: {result}");
assert!(!result.contains("retry_count: \"0\""), "integer must not be quoted: {result}");
}
#[test]
fn update_story_bool_front_matter_parseable_after_write() {
let tmp = tempfile::tempdir().unwrap();
let current = tmp.path().join(".storkit/work/2_current");
fs::create_dir_all(&current).unwrap();
let filepath = current.join("29_test.md");
fs::write(&filepath, "---\nname: My Story\n---\n\nNo sections.\n").unwrap();
let mut fields = HashMap::new();
fields.insert("blocked".to_string(), "false".to_string());
update_story_in_file(tmp.path(), "29_test", None, None, Some(&fields)).unwrap();
let contents = fs::read_to_string(&filepath).unwrap();
let meta = parse_front_matter(&contents).expect("front matter should parse");
assert_eq!(meta.name.as_deref(), Some("My Story"), "name preserved after writing bool field");
}
}