77 lines
2.1 KiB
Rust
77 lines
2.1 KiB
Rust
|
|
//! 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<String, String> {
|
||
|
|
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)
|
||
|
|
}
|