huskies: merge 573_story_remove_criterion_mcp_tool_to_delete_an_acceptance_criterion
This commit is contained in:
@@ -126,6 +126,53 @@ pub fn check_criterion_in_file(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove an acceptance criterion from a story by its 0-based index (counting all criteria,
|
||||
/// both checked and unchecked). Returns an error if the index is out of range.
|
||||
pub fn remove_criterion_from_file(
|
||||
project_root: &Path,
|
||||
story_id: &str,
|
||||
criterion_index: usize,
|
||||
) -> Result<(), String> {
|
||||
let contents = read_story_content(project_root, story_id)?;
|
||||
|
||||
let mut count: usize = 0;
|
||||
let mut found = false;
|
||||
let new_lines: Vec<String> = contents
|
||||
.lines()
|
||||
.filter(|line| {
|
||||
let trimmed = line.trim();
|
||||
if trimmed.starts_with("- [ ] ") || trimmed.starts_with("- [x] ") {
|
||||
if count == criterion_index {
|
||||
count += 1;
|
||||
found = true;
|
||||
return false;
|
||||
}
|
||||
count += 1;
|
||||
}
|
||||
true
|
||||
})
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
|
||||
if !found {
|
||||
return Err(format!(
|
||||
"Criterion index {criterion_index} out of range. Story '{story_id}' has \
|
||||
{count} criteria (indices 0..{}).",
|
||||
count.saturating_sub(1)
|
||||
));
|
||||
}
|
||||
|
||||
let mut new_str = new_lines.join("\n");
|
||||
if contents.ends_with('\n') {
|
||||
new_str.push('\n');
|
||||
}
|
||||
|
||||
let stage = story_stage(story_id).unwrap_or_else(|| "2_current".to_string());
|
||||
write_story_content(project_root, story_id, &stage, &new_str);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Edit the text of an existing acceptance criterion without changing its checked state.
|
||||
///
|
||||
/// Finds the criterion at `criterion_index` (0-based, counting all criteria regardless
|
||||
@@ -578,6 +625,61 @@ mod tests {
|
||||
assert!(result.unwrap_err().contains("Acceptance Criteria"));
|
||||
}
|
||||
|
||||
// ── remove_criterion_from_file tests ──────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn remove_criterion_removes_by_index() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
setup_story_in_fs(
|
||||
tmp.path(),
|
||||
"20_remove_test",
|
||||
&story_with_ac_section(&["First", "Second", "Third"]),
|
||||
);
|
||||
|
||||
remove_criterion_from_file(tmp.path(), "20_remove_test", 1).unwrap();
|
||||
|
||||
let contents = read_story_content(tmp.path(), "20_remove_test").unwrap();
|
||||
assert!(contents.contains("- [ ] First"), "First should remain");
|
||||
assert!(!contents.contains("Second"), "Second should be removed");
|
||||
assert!(contents.contains("- [ ] Third"), "Third should remain");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_criterion_shifts_indices() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
setup_story_in_fs(
|
||||
tmp.path(),
|
||||
"21_remove_test",
|
||||
&story_with_ac_section(&["A", "B", "C"]),
|
||||
);
|
||||
|
||||
remove_criterion_from_file(tmp.path(), "21_remove_test", 0).unwrap();
|
||||
|
||||
let contents = read_story_content(tmp.path(), "21_remove_test").unwrap();
|
||||
assert!(!contents.contains("- [ ] A"), "A should be removed");
|
||||
assert!(contents.contains("- [ ] B"), "B should remain");
|
||||
assert!(contents.contains("- [ ] C"), "C should remain");
|
||||
// B is now at index 0, C at index 1 — verify by removing B next
|
||||
remove_criterion_from_file(tmp.path(), "21_remove_test", 0).unwrap();
|
||||
let contents2 = read_story_content(tmp.path(), "21_remove_test").unwrap();
|
||||
assert!(!contents2.contains("- [ ] B"), "B should now be removed");
|
||||
assert!(contents2.contains("- [ ] C"), "C should still remain");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_criterion_out_of_range_returns_error() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
setup_story_in_fs(
|
||||
tmp.path(),
|
||||
"22_remove_test",
|
||||
&story_with_ac_section(&["Only"]),
|
||||
);
|
||||
|
||||
let result = remove_criterion_from_file(tmp.path(), "22_remove_test", 5);
|
||||
assert!(result.is_err(), "should fail for out-of-range index");
|
||||
assert!(result.unwrap_err().contains("out of range"));
|
||||
}
|
||||
|
||||
// ── update_story_in_file tests ─────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user