//! Epic work-item creation operations. //! //! Epics are shared-context containers that group related stories, bugs, spikes, and //! refactors under a common goal. They are stored in the CRDT items list with //! `type: epic` and are not pipeline-driven (no stage advancement). use std::path::Path; use super::super::{next_item_number, slugify_name, write_story_content}; /// Create an epic file and store it in the database. /// /// Returns the epic_id (e.g. `"880"`). pub fn create_epic_file( root: &Path, name: &str, goal: &str, motivation: Option<&str>, key_files: Option<&str>, success_criteria: Option<&[String]>, ) -> Result { let epic_number = next_item_number(root)?; let slug = slugify_name(name); if slug.is_empty() { return Err("Name must contain at least one alphanumeric character.".to_string()); } let epic_id = format!("{epic_number}"); let mut content = String::new(); content.push_str("---\n"); content.push_str("type: epic\n"); content.push_str(&format!("name: \"{}\"\n", name.replace('"', "\\\""))); content.push_str("---\n\n"); content.push_str(&format!("# Epic {epic_number}: {name}\n\n")); content.push_str("## Goal\n\n"); content.push_str(goal); content.push_str("\n\n"); content.push_str("## Motivation\n\n"); if let Some(m) = motivation { content.push_str(m); content.push('\n'); } else { content.push_str("- TBD\n"); } content.push('\n'); content.push_str("## Key Files\n\n"); if let Some(kf) = key_files { content.push_str(kf); content.push('\n'); } else { content.push_str("- TBD\n"); } content.push('\n'); content.push_str("## Success Criteria\n\n"); match success_criteria { Some(criteria) if !criteria.is_empty() => { for c in criteria { content.push_str(&format!("- {c}\n")); } } _ => { content.push_str("- TBD\n"); } } // Epics are stored in backlog (no pipeline advancement). write_story_content(root, &epic_id, "1_backlog", &content); Ok(epic_id) }