huskies: merge 942

This commit is contained in:
dave
2026-05-13 05:16:11 +00:00
parent 7ca5339450
commit 0a825b9f27
11 changed files with 416 additions and 258 deletions
+49
View File
@@ -222,6 +222,55 @@ pub(crate) fn next_item_number(_root: &std::path::Path) -> Result<u32, String> {
Ok(crate::db::next_item_number())
}
/// Single internal entry point for creating a new pipeline work item in the backlog.
///
/// This is the canonical creation path. All `create_*_file` functions for pipeline
/// item types (story, bug, spike, refactor) route through here. On validation failure
/// this function returns `Err` and writes nothing.
///
/// Validates:
/// - `name` is not empty or whitespace-only
/// - `name` contains at least one alphanumeric character
/// - `acceptance_criteria` has at least one entry
/// - `item_type` is a known pipeline item type
///
/// `build_content` receives the assigned item number and returns the full markdown
/// content to persist (including front matter and all type-specific sections).
pub(crate) fn create_item_in_backlog(
root: &Path,
item_type: &str,
name: &str,
acceptance_criteria: &[String],
depends_on: Option<&[u32]>,
build_content: impl FnOnce(u32) -> String,
) -> Result<String, String> {
if name.trim().is_empty() {
return Err("Title must not be empty or whitespace-only.".to_string());
}
if slugify_name(name).is_empty() {
return Err("Title must contain at least one alphanumeric character.".to_string());
}
if acceptance_criteria.is_empty() {
return Err("At least one acceptance criterion is required.".to_string());
}
const VALID_TYPES: &[&str] = &["story", "bug", "spike", "refactor"];
if !VALID_TYPES.contains(&item_type) {
return Err(format!(
"Invalid item type '{item_type}': must be one of story, bug, spike, refactor."
));
}
let item_number = next_item_number(root)?;
let item_id = format!("{item_number}");
let content = build_content(item_number);
write_story_content(root, &item_id, "1_backlog", &content, Some(name));
crate::crdt_state::set_depends_on(&item_id, depends_on.unwrap_or(&[]));
crate::crdt_state::set_item_type(&item_id, Some(item_type));
Ok(item_id)
}
#[cfg(test)]
mod tests {
use super::*;