fix: add --all to cargo fmt in script/test and autoformat codebase

cargo fmt without --all fails with "Failed to find targets" in
workspace repos. This was blocking every story's gates. Also ran
cargo fmt --all to fix all existing formatting issues.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
dave
2026-04-13 14:07:08 +00:00
parent ed2526ce41
commit 845b85e7a7
128 changed files with 3566 additions and 2395 deletions
+271 -66
View File
@@ -4,7 +4,10 @@ use serde_json::Value;
use std::collections::HashMap;
use std::path::Path;
use super::{create_section_content, next_item_number, read_story_content, replace_section_content, slugify_name, story_stage, write_story_content};
use super::{
create_section_content, next_item_number, read_story_content, replace_section_content,
slugify_name, story_stage, write_story_content,
};
/// Shared create-story logic used by both the OpenApi and MCP handlers.
///
@@ -158,9 +161,7 @@ pub fn add_criterion_to_file(
let insert_after = last_criterion_line
.or(ac_section_start)
.ok_or_else(|| {
format!("Story '{story_id}' has no '## Acceptance Criteria' section.")
})?;
.ok_or_else(|| format!("Story '{story_id}' has no '## Acceptance Criteria' section."))?;
let mut new_lines: Vec<String> = lines.iter().map(|s| s.to_string()).collect();
new_lines.insert(insert_after + 1, format!("- [ ] {criterion}"));
@@ -195,7 +196,14 @@ fn json_value_to_yaml_scalar(value: &Value) -> String {
}
Value::String(s) => yaml_encode_str(s),
// Null and Object are not meaningful as YAML scalars; store as quoted strings.
other => format!("\"{}\"", other.to_string().replace('"', "\\\"").replace('\n', " ").replace('\r', "")),
other => format!(
"\"{}\"",
other
.to_string()
.replace('"', "\\\"")
.replace('\n', " ")
.replace('\r', "")
),
}
}
@@ -211,7 +219,10 @@ fn yaml_encode_str(s: &str) -> String {
// YAML inline sequences like [490] or [490, 491] — write unquoted so
// serde_yaml can deserialise them as Vec<u32>.
s if s.starts_with('[') && s.ends_with(']') => s.to_string(),
s => format!("\"{}\"", s.replace('"', "\\\"").replace('\n', " ").replace('\r', "")),
s => format!(
"\"{}\"",
s.replace('"', "\\\"").replace('\n', " ").replace('\r', "")
),
}
}
@@ -246,13 +257,17 @@ pub fn update_story_in_file(
if let Some(us) = user_story {
contents = match replace_section_content(&contents, "User Story", us) {
Ok(updated) => updated,
Err(_) => create_section_content(&contents, "User Story", us, Some("Acceptance Criteria")),
Err(_) => {
create_section_content(&contents, "User Story", us, Some("Acceptance Criteria"))
}
};
}
if let Some(desc) = description {
contents = match replace_section_content(&contents, "Description", desc) {
Ok(updated) => updated,
Err(_) => create_section_content(&contents, "Description", desc, Some("Acceptance Criteria")),
Err(_) => {
create_section_content(&contents, "Description", desc, Some("Acceptance Criteria"))
}
};
}
@@ -322,7 +337,11 @@ mod tests {
fs::create_dir_all(&backlog).unwrap();
fs::write(backlog.join("36_story_existing.md"), "").unwrap();
// Also write to content store so next_item_number sees it.
crate::db::write_item_with_content("36_story_existing", "1_backlog", "---\nname: Existing\n---\n");
crate::db::write_item_with_content(
"36_story_existing",
"1_backlog",
"---\nname: Existing\n---\n",
);
let number = super::super::next_item_number(tmp.path()).unwrap();
// The number must be >= 37 (at least higher than the existing "36_story_existing.md"),
@@ -390,9 +409,18 @@ mod tests {
// Read the updated content.
let contents = read_story_content(tmp.path(), "1_test").unwrap();
assert!(contents.contains("- [x] Criterion 0"), "first should be checked");
assert!(contents.contains("- [ ] Criterion 1"), "second should stay unchecked");
assert!(contents.contains("- [ ] Criterion 2"), "third should stay unchecked");
assert!(
contents.contains("- [x] Criterion 0"),
"first should be checked"
);
assert!(
contents.contains("- [ ] Criterion 1"),
"second should stay unchecked"
);
assert!(
contents.contains("- [ ] Criterion 2"),
"third should stay unchecked"
);
}
#[test]
@@ -404,9 +432,18 @@ mod tests {
check_criterion_in_file(tmp.path(), "2_test", 1).unwrap();
let contents = read_story_content(tmp.path(), "2_test").unwrap();
assert!(contents.contains("- [ ] Criterion 0"), "first should stay unchecked");
assert!(contents.contains("- [x] Criterion 1"), "second should be checked");
assert!(contents.contains("- [ ] Criterion 2"), "third should stay unchecked");
assert!(
contents.contains("- [ ] Criterion 0"),
"first should stay unchecked"
);
assert!(
contents.contains("- [x] Criterion 1"),
"second should be checked"
);
assert!(
contents.contains("- [ ] Criterion 2"),
"third should stay unchecked"
);
}
#[test]
@@ -423,7 +460,9 @@ mod tests {
// ── add_criterion_to_file tests ───────────────────────────────────────────
fn story_with_ac_section(criteria: &[&str]) -> String {
let mut s = "---\nname: Test\n---\n\n## User Story\n\nAs a user...\n\n## Acceptance Criteria\n\n".to_string();
let mut s =
"---\nname: Test\n---\n\n## User Story\n\nAs a user...\n\n## Acceptance Criteria\n\n"
.to_string();
for c in criteria {
s.push_str(&format!("- [ ] {c}\n"));
}
@@ -434,7 +473,11 @@ mod tests {
#[test]
fn add_criterion_appends_after_last_criterion() {
let tmp = tempfile::tempdir().unwrap();
setup_story_in_fs(tmp.path(), "10_test", &story_with_ac_section(&["First", "Second"]));
setup_story_in_fs(
tmp.path(),
"10_test",
&story_with_ac_section(&["First", "Second"]),
);
add_criterion_to_file(tmp.path(), "10_test", "Third").unwrap();
@@ -450,19 +493,27 @@ mod tests {
#[test]
fn add_criterion_to_empty_section() {
let tmp = tempfile::tempdir().unwrap();
let content = "---\nname: Test\n---\n\n## Acceptance Criteria\n\n## Out of Scope\n\n- N/A\n";
let content =
"---\nname: Test\n---\n\n## Acceptance Criteria\n\n## Out of Scope\n\n- N/A\n";
setup_story_in_fs(tmp.path(), "11_test", content);
add_criterion_to_file(tmp.path(), "11_test", "New AC").unwrap();
let contents = read_story_content(tmp.path(), "11_test").unwrap();
assert!(contents.contains("- [ ] New AC\n"), "criterion should be present");
assert!(
contents.contains("- [ ] New AC\n"),
"criterion should be present"
);
}
#[test]
fn add_criterion_missing_section_returns_error() {
let tmp = tempfile::tempdir().unwrap();
setup_story_in_fs(tmp.path(), "12_test", "---\nname: Test\n---\n\nNo AC section here.\n");
setup_story_in_fs(
tmp.path(),
"12_test",
"---\nname: Test\n---\n\nNo AC section here.\n",
);
let result = add_criterion_to_file(tmp.path(), "12_test", "X");
assert!(result.is_err());
@@ -477,12 +528,25 @@ mod tests {
let content = "---\nname: T\n---\n\n## User Story\n\nOld text\n\n## Acceptance Criteria\n\n- [ ] AC\n";
setup_story_in_fs(tmp.path(), "20_test", content);
update_story_in_file(tmp.path(), "20_test", Some("New user story text"), None, None).unwrap();
update_story_in_file(
tmp.path(),
"20_test",
Some("New user story text"),
None,
None,
)
.unwrap();
let result = read_story_content(tmp.path(), "20_test").unwrap();
assert!(result.contains("New user story text"), "new text should be present");
assert!(
result.contains("New user story text"),
"new text should be present"
);
assert!(!result.contains("Old text"), "old text should be replaced");
assert!(result.contains("## Acceptance Criteria"), "other sections preserved");
assert!(
result.contains("## Acceptance Criteria"),
"other sections preserved"
);
}
#[test]
@@ -494,8 +558,14 @@ mod tests {
update_story_in_file(tmp.path(), "21_test", None, Some("New description"), None).unwrap();
let result = read_story_content(tmp.path(), "21_test").unwrap();
assert!(result.contains("New description"), "new description present");
assert!(!result.contains("Old description"), "old description replaced");
assert!(
result.contains("New description"),
"new description present"
);
assert!(
!result.contains("Old description"),
"old description replaced"
);
}
#[test]
@@ -515,16 +585,26 @@ mod tests {
let content = "---\nname: T\n---\n\n## Acceptance Criteria\n\n- [ ] AC\n";
setup_story_in_fs(tmp.path(), "23_test", content);
let result = update_story_in_file(tmp.path(), "23_test", Some("New user story"), None, None);
assert!(result.is_ok(), "should succeed when section is missing: {result:?}");
let result =
update_story_in_file(tmp.path(), "23_test", Some("New user story"), None, None);
assert!(
result.is_ok(),
"should succeed when section is missing: {result:?}"
);
let updated = read_story_content(tmp.path(), "23_test").unwrap();
assert!(updated.contains("## User Story"), "section should be created");
assert!(
updated.contains("## User Story"),
"section should be created"
);
assert!(updated.contains("New user story"), "text should be present");
// Section should appear before Acceptance Criteria.
let pos_us = updated.find("## User Story").unwrap();
let pos_ac = updated.find("## Acceptance Criteria").unwrap();
assert!(pos_us < pos_ac, "User Story should be before Acceptance Criteria");
assert!(
pos_us < pos_ac,
"User Story should be before Acceptance Criteria"
);
}
#[test]
@@ -534,16 +614,34 @@ mod tests {
let content = "---\nname: T\n---\n\n## User Story\n\nAs a user...\n\n## Acceptance Criteria\n\n- [ ] AC\n";
setup_story_in_fs(tmp.path(), "32_test", content);
let result = update_story_in_file(tmp.path(), "32_test", None, Some("New description text"), None);
assert!(result.is_ok(), "should succeed when section is missing: {result:?}");
let result = update_story_in_file(
tmp.path(),
"32_test",
None,
Some("New description text"),
None,
);
assert!(
result.is_ok(),
"should succeed when section is missing: {result:?}"
);
let updated = read_story_content(tmp.path(), "32_test").unwrap();
assert!(updated.contains("## Description"), "section should be created");
assert!(updated.contains("New description text"), "text should be present");
assert!(
updated.contains("## Description"),
"section should be created"
);
assert!(
updated.contains("New description text"),
"text should be present"
);
// Section should appear before Acceptance Criteria.
let pos_desc = updated.find("## Description").unwrap();
let pos_ac = updated.find("## Acceptance Criteria").unwrap();
assert!(pos_desc < pos_ac, "Description should be before Acceptance Criteria");
assert!(
pos_desc < pos_ac,
"Description should be before Acceptance Criteria"
);
}
#[test]
@@ -553,32 +651,58 @@ mod tests {
let content = "---\nname: T\n---\n\nSome content here.\n";
setup_story_in_fs(tmp.path(), "33_test", content);
let result = update_story_in_file(tmp.path(), "33_test", None, Some("Appended description"), None);
assert!(result.is_ok(), "should succeed even with no Acceptance Criteria: {result:?}");
let result = update_story_in_file(
tmp.path(),
"33_test",
None,
Some("Appended description"),
None,
);
assert!(
result.is_ok(),
"should succeed even with no Acceptance Criteria: {result:?}"
);
let updated = read_story_content(tmp.path(), "33_test").unwrap();
assert!(updated.contains("## Description"), "section should be created");
assert!(updated.contains("Appended description"), "text should be present");
assert!(
updated.contains("## Description"),
"section should be created"
);
assert!(
updated.contains("Appended description"),
"text should be present"
);
}
#[test]
fn update_story_sets_agent_front_matter_field() {
let tmp = tempfile::tempdir().unwrap();
setup_story_in_fs(tmp.path(), "24_test", "---\nname: T\n---\n\n## User Story\n\nSome story\n");
setup_story_in_fs(
tmp.path(),
"24_test",
"---\nname: T\n---\n\n## User Story\n\nSome story\n",
);
let mut fields = HashMap::new();
fields.insert("agent".to_string(), Value::String("dev".to_string()));
update_story_in_file(tmp.path(), "24_test", None, None, Some(&fields)).unwrap();
let result = read_story_content(tmp.path(), "24_test").unwrap();
assert!(result.contains("agent: \"dev\""), "agent field should be set");
assert!(
result.contains("agent: \"dev\""),
"agent field should be set"
);
assert!(result.contains("name: T"), "name field preserved");
}
#[test]
fn update_story_sets_arbitrary_front_matter_fields() {
let tmp = tempfile::tempdir().unwrap();
setup_story_in_fs(tmp.path(), "25_test", "---\nname: T\n---\n\n## User Story\n\nSome story\n");
setup_story_in_fs(
tmp.path(),
"25_test",
"---\nname: T\n---\n\n## User Story\n\nSome story\n",
);
let mut fields = HashMap::new();
fields.insert("qa".to_string(), Value::String("human".to_string()));
@@ -587,19 +711,29 @@ mod tests {
let result = read_story_content(tmp.path(), "25_test").unwrap();
assert!(result.contains("qa: \"human\""), "qa field should be set");
assert!(result.contains("priority: \"high\""), "priority field should be set");
assert!(
result.contains("priority: \"high\""),
"priority field should be set"
);
assert!(result.contains("name: T"), "name field preserved");
}
#[test]
fn update_story_front_matter_only_no_section_required() {
let tmp = tempfile::tempdir().unwrap();
setup_story_in_fs(tmp.path(), "26_test", "---\nname: T\n---\n\nNo sections here.\n");
setup_story_in_fs(
tmp.path(),
"26_test",
"---\nname: T\n---\n\nNo sections here.\n",
);
let mut fields = HashMap::new();
fields.insert("agent".to_string(), Value::String("dev".to_string()));
let result = update_story_in_file(tmp.path(), "26_test", None, None, Some(&fields));
assert!(result.is_ok(), "front-matter-only update should not require body sections");
assert!(
result.is_ok(),
"front-matter-only update should not require body sections"
);
let contents = read_story_content(tmp.path(), "26_test").unwrap();
assert!(contents.contains("agent: \"dev\""));
@@ -616,8 +750,14 @@ mod tests {
update_story_in_file(tmp.path(), "27_test", None, None, Some(&fields)).unwrap();
let result = read_story_content(tmp.path(), "27_test").unwrap();
assert!(result.contains("blocked: false"), "bool should be unquoted: {result}");
assert!(!result.contains("blocked: \"false\""), "bool must not be quoted: {result}");
assert!(
result.contains("blocked: false"),
"bool should be unquoted: {result}"
);
assert!(
!result.contains("blocked: \"false\""),
"bool must not be quoted: {result}"
);
}
#[test]
@@ -631,14 +771,24 @@ mod tests {
update_story_in_file(tmp.path(), "28_test", None, None, Some(&fields)).unwrap();
let result = read_story_content(tmp.path(), "28_test").unwrap();
assert!(result.contains("retry_count: 0"), "integer should be unquoted: {result}");
assert!(!result.contains("retry_count: \"0\""), "integer must not be quoted: {result}");
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();
setup_story_in_fs(tmp.path(), "29_test", "---\nname: My Story\n---\n\nNo sections.\n");
setup_story_in_fs(
tmp.path(),
"29_test",
"---\nname: My Story\n---\n\nNo sections.\n",
);
let mut fields = HashMap::new();
fields.insert("blocked".to_string(), Value::String("false".to_string()));
@@ -646,7 +796,11 @@ mod tests {
let contents = read_story_content(tmp.path(), "29_test").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");
assert_eq!(
meta.name.as_deref(),
Some("My Story"),
"name preserved after writing bool field"
);
}
// ── Bug 493 regression tests ──────────────────────────────────────────────
@@ -662,8 +816,14 @@ mod tests {
update_story_in_file(tmp.path(), "30_test", None, None, Some(&fields)).unwrap();
let result = read_story_content(tmp.path(), "30_test").unwrap();
assert!(result.contains("depends_on: [490]"), "should be unquoted array: {result}");
assert!(!result.contains("depends_on: \"[490]\""), "must not be quoted: {result}");
assert!(
result.contains("depends_on: [490]"),
"should be unquoted array: {result}"
);
assert!(
!result.contains("depends_on: \"[490]\""),
"must not be quoted: {result}"
);
let meta = parse_front_matter(&result).expect("front matter should parse");
assert_eq!(meta.depends_on, Some(vec![490]));
@@ -690,8 +850,14 @@ mod tests {
})
.expect("story content should exist");
assert!(contents.contains("depends_on: [489]"), "missing front matter: {contents}");
assert!(!contents.contains("- [ ] depends_on"), "must not appear as checkbox: {contents}");
assert!(
contents.contains("depends_on: [489]"),
"missing front matter: {contents}"
);
assert!(
!contents.contains("- [ ] depends_on"),
"must not appear as checkbox: {contents}"
);
let meta = parse_front_matter(&contents).expect("front matter should parse");
assert_eq!(meta.depends_on, Some(vec![489]));
@@ -709,8 +875,14 @@ mod tests {
update_story_in_file(tmp.path(), "31_test", None, None, Some(&fields)).unwrap();
let result = read_story_content(tmp.path(), "31_test").unwrap();
assert!(result.contains("blocked: false"), "native bool false should be unquoted: {result}");
assert!(!result.contains("blocked: \"false\""), "must not be quoted: {result}");
assert!(
result.contains("blocked: false"),
"native bool false should be unquoted: {result}"
);
assert!(
!result.contains("blocked: \"false\""),
"must not be quoted: {result}"
);
}
#[test]
@@ -723,22 +895,38 @@ mod tests {
update_story_in_file(tmp.path(), "32_test", None, None, Some(&fields)).unwrap();
let result = read_story_content(tmp.path(), "32_test").unwrap();
assert!(result.contains("blocked: true"), "native bool true should be unquoted: {result}");
assert!(!result.contains("blocked: \"true\""), "must not be quoted: {result}");
assert!(
result.contains("blocked: true"),
"native bool true should be unquoted: {result}"
);
assert!(
!result.contains("blocked: \"true\""),
"must not be quoted: {result}"
);
}
#[test]
fn update_story_native_integer_written_unquoted() {
let tmp = tempfile::tempdir().unwrap();
setup_story_in_fs(tmp.path(), "33b_test", "---\nname: T\n---\n\nNo sections.\n");
setup_story_in_fs(
tmp.path(),
"33b_test",
"---\nname: T\n---\n\nNo sections.\n",
);
let mut fields = HashMap::new();
fields.insert("retry_count".to_string(), serde_json::json!(3));
update_story_in_file(tmp.path(), "33b_test", None, None, Some(&fields)).unwrap();
let result = read_story_content(tmp.path(), "33b_test").unwrap();
assert!(result.contains("retry_count: 3"), "native integer should be unquoted: {result}");
assert!(!result.contains("retry_count: \"3\""), "must not be quoted: {result}");
assert!(
result.contains("retry_count: 3"),
"native integer should be unquoted: {result}"
);
assert!(
!result.contains("retry_count: \"3\""),
"must not be quoted: {result}"
);
}
#[test]
@@ -751,8 +939,14 @@ mod tests {
update_story_in_file(tmp.path(), "34_test", None, None, Some(&fields)).unwrap();
let result = read_story_content(tmp.path(), "34_test").unwrap();
assert!(result.contains("depends_on: [490, 491]"), "native array should be YAML sequence: {result}");
assert!(!result.contains("depends_on: \"["), "must not be quoted: {result}");
assert!(
result.contains("depends_on: [490, 491]"),
"native array should be YAML sequence: {result}"
);
assert!(
!result.contains("depends_on: \"["),
"must not be quoted: {result}"
);
let meta = parse_front_matter(&result).expect("front matter should parse");
assert_eq!(meta.depends_on, Some(vec![490, 491]));
@@ -761,7 +955,11 @@ mod tests {
#[test]
fn update_story_native_bool_parseable_after_write() {
let tmp = tempfile::tempdir().unwrap();
setup_story_in_fs(tmp.path(), "35_test", "---\nname: My Story\n---\n\nNo sections.\n");
setup_story_in_fs(
tmp.path(),
"35_test",
"---\nname: My Story\n---\n\nNo sections.\n",
);
let mut fields = HashMap::new();
fields.insert("blocked".to_string(), Value::Bool(false));
@@ -769,7 +967,11 @@ mod tests {
let contents = read_story_content(tmp.path(), "35_test").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 native bool");
assert_eq!(
meta.name.as_deref(),
Some("My Story"),
"name preserved after writing native bool"
);
}
#[test]
@@ -779,7 +981,10 @@ mod tests {
// String "[490, 491]" still works (backwards compatibility).
let mut fields = HashMap::new();
fields.insert("depends_on".to_string(), Value::String("[490, 491]".to_string()));
fields.insert(
"depends_on".to_string(),
Value::String("[490, 491]".to_string()),
);
update_story_in_file(tmp.path(), "31_test", None, None, Some(&fields)).unwrap();
let result = read_story_content(tmp.path(), "31_test").unwrap();