huskies: merge 730_story_use_numeric_only_story_ids_across_mcp_worktrees_git_branches_and_log_paths
This commit is contained in:
@@ -7,7 +7,7 @@ use super::{next_item_number, slugify_name, write_story_content};
|
||||
/// Create a bug file and store it in the database.
|
||||
///
|
||||
/// Also writes to the filesystem for backwards compatibility during migration.
|
||||
/// Returns the bug_id (e.g. `"4_bug_login_crash"`).
|
||||
/// Returns the bug_id (e.g. `"4"`).
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn create_bug_file(
|
||||
root: &Path,
|
||||
@@ -26,10 +26,11 @@ pub fn create_bug_file(
|
||||
return Err("Name must contain at least one alphanumeric character.".to_string());
|
||||
}
|
||||
|
||||
let bug_id = format!("{bug_number}_bug_{slug}");
|
||||
let bug_id = format!("{bug_number}");
|
||||
|
||||
let mut content = String::new();
|
||||
content.push_str("---\n");
|
||||
content.push_str("type: bug\n");
|
||||
content.push_str(&format!("name: \"{}\"\n", name.replace('"', "\\\"")));
|
||||
if let Some(deps) = depends_on.filter(|d| !d.is_empty()) {
|
||||
let nums: Vec<String> = deps.iter().map(|n| n.to_string()).collect();
|
||||
@@ -66,7 +67,7 @@ pub fn create_bug_file(
|
||||
|
||||
/// Create a spike file and store it in the database.
|
||||
///
|
||||
/// Returns the spike_id (e.g. `"4_spike_filesystem_watcher_architecture"`).
|
||||
/// Returns the spike_id (e.g. `"4"`).
|
||||
pub fn create_spike_file(
|
||||
root: &Path,
|
||||
name: &str,
|
||||
@@ -80,10 +81,11 @@ pub fn create_spike_file(
|
||||
return Err("Name must contain at least one alphanumeric character.".to_string());
|
||||
}
|
||||
|
||||
let spike_id = format!("{spike_number}_spike_{slug}");
|
||||
let spike_id = format!("{spike_number}");
|
||||
|
||||
let mut content = String::new();
|
||||
content.push_str("---\n");
|
||||
content.push_str("type: spike\n");
|
||||
content.push_str(&format!("name: \"{}\"\n", name.replace('"', "\\\"")));
|
||||
content.push_str("---\n\n");
|
||||
content.push_str(&format!("# Spike {spike_number}: {name}\n\n"));
|
||||
@@ -122,7 +124,7 @@ pub fn create_spike_file(
|
||||
|
||||
/// Create a refactor work item and store it in the database.
|
||||
///
|
||||
/// Returns the refactor_id (e.g. `"5_refactor_split_agents_rs"`).
|
||||
/// Returns the refactor_id (e.g. `"5"`).
|
||||
pub fn create_refactor_file(
|
||||
root: &Path,
|
||||
name: &str,
|
||||
@@ -137,10 +139,11 @@ pub fn create_refactor_file(
|
||||
return Err("Name must contain at least one alphanumeric character.".to_string());
|
||||
}
|
||||
|
||||
let refactor_id = format!("{refactor_number}_refactor_{slug}");
|
||||
let refactor_id = format!("{refactor_number}");
|
||||
|
||||
let mut content = String::new();
|
||||
content.push_str("---\n");
|
||||
content.push_str("type: refactor\n");
|
||||
content.push_str(&format!("name: \"{}\"\n", name.replace('"', "\\\"")));
|
||||
if let Some(deps) = depends_on.filter(|d| !d.is_empty()) {
|
||||
let nums: Vec<String> = deps.iter().map(|n| n.to_string()).collect();
|
||||
@@ -176,10 +179,24 @@ pub fn create_refactor_file(
|
||||
Ok(refactor_id)
|
||||
}
|
||||
|
||||
/// Returns true if the item stem (filename without extension) is a bug item.
|
||||
/// Returns true if the item stem is a bug item.
|
||||
///
|
||||
/// Checks the slug-based ID format first (e.g. `"4_bug_login_crash"`), then
|
||||
/// falls back to reading `type: bug` from the content store for numeric-only IDs.
|
||||
fn is_bug_item(stem: &str) -> bool {
|
||||
let after_num = stem.trim_start_matches(|c: char| c.is_ascii_digit());
|
||||
after_num.starts_with("_bug_")
|
||||
if after_num.starts_with("_bug_") {
|
||||
return true;
|
||||
}
|
||||
// Numeric-only ID: check content store front matter.
|
||||
if after_num.is_empty() {
|
||||
return crate::db::read_content(stem)
|
||||
.and_then(|c| parse_front_matter(&c).ok())
|
||||
.and_then(|m| m.item_type)
|
||||
.map(|t| t == "bug")
|
||||
.unwrap_or(false);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Extract bug name from content (heading or front matter).
|
||||
@@ -229,9 +246,23 @@ pub fn list_bug_files(_root: &Path) -> Result<Vec<(String, String)>, String> {
|
||||
}
|
||||
|
||||
/// Returns true if the item stem is a refactor item.
|
||||
///
|
||||
/// Checks the slug-based ID format first (e.g. `"5_refactor_split_agents_rs"`), then
|
||||
/// falls back to reading `type: refactor` from the content store for numeric-only IDs.
|
||||
fn is_refactor_item(stem: &str) -> bool {
|
||||
let after_num = stem.trim_start_matches(|c: char| c.is_ascii_digit());
|
||||
after_num.starts_with("_refactor_")
|
||||
if after_num.starts_with("_refactor_") {
|
||||
return true;
|
||||
}
|
||||
// Numeric-only ID: check content store front matter.
|
||||
if after_num.is_empty() {
|
||||
return crate::db::read_content(stem)
|
||||
.and_then(|c| parse_front_matter(&c).ok())
|
||||
.and_then(|m| m.item_type)
|
||||
.map(|t| t == "refactor")
|
||||
.unwrap_or(false);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// List all open refactors from CRDT + content store.
|
||||
@@ -431,8 +462,8 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
bug_id.ends_with("_bug_login_crash"),
|
||||
"expected ID to end with _bug_login_crash, got: {bug_id}"
|
||||
bug_id.chars().all(|c| c.is_ascii_digit()),
|
||||
"bug ID must be numeric-only, got: {bug_id}"
|
||||
);
|
||||
|
||||
// Check content exists (either in DB or filesystem).
|
||||
@@ -446,8 +477,8 @@ mod tests {
|
||||
.expect("bug content should exist");
|
||||
|
||||
assert!(
|
||||
contents.starts_with("---\nname: \"Login Crash\"\n---"),
|
||||
"bug file must start with YAML front matter"
|
||||
contents.starts_with("---\ntype: bug\nname: \"Login Crash\"\n---"),
|
||||
"bug file must start with YAML front matter including type field"
|
||||
);
|
||||
assert!(
|
||||
contents.contains("Login Crash"),
|
||||
@@ -499,16 +530,11 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let contents = crate::db::read_content(&bug_id)
|
||||
.or_else(|| {
|
||||
let filepath = tmp.path().join(".huskies/work/1_backlog/1_bug_some_bug.md");
|
||||
fs::read_to_string(filepath).ok()
|
||||
})
|
||||
.expect("bug content should exist");
|
||||
let contents = crate::db::read_content(&bug_id).expect("bug content should exist");
|
||||
|
||||
assert!(
|
||||
contents.starts_with("---\nname: \"Some Bug\"\n---"),
|
||||
"bug file must have YAML front matter"
|
||||
contents.starts_with("---\ntype: bug\nname: \"Some Bug\"\n---"),
|
||||
"bug file must have YAML front matter with type field"
|
||||
);
|
||||
assert!(contents.contains("- [ ] Bug is fixed and verified"));
|
||||
}
|
||||
@@ -523,22 +549,16 @@ mod tests {
|
||||
create_spike_file(tmp.path(), "Filesystem Watcher Architecture", None, &[]).unwrap();
|
||||
|
||||
assert!(
|
||||
spike_id.ends_with("_spike_filesystem_watcher_architecture"),
|
||||
"expected ID to end with _spike_filesystem_watcher_architecture, got: {spike_id}"
|
||||
spike_id.chars().all(|c| c.is_ascii_digit()),
|
||||
"spike ID must be numeric-only, got: {spike_id}"
|
||||
);
|
||||
|
||||
let contents = crate::db::read_content(&spike_id)
|
||||
.or_else(|| {
|
||||
let filepath = tmp
|
||||
.path()
|
||||
.join(format!(".huskies/work/1_backlog/{spike_id}.md"));
|
||||
fs::read_to_string(filepath).ok()
|
||||
})
|
||||
.expect("spike content should exist");
|
||||
let contents = crate::db::read_content(&spike_id).expect("spike content should exist");
|
||||
|
||||
assert!(
|
||||
contents.starts_with("---\nname: \"Filesystem Watcher Architecture\"\n---"),
|
||||
"spike file must start with YAML front matter"
|
||||
contents
|
||||
.starts_with("---\ntype: spike\nname: \"Filesystem Watcher Architecture\"\n---"),
|
||||
"spike file must start with YAML front matter including type field"
|
||||
);
|
||||
assert!(
|
||||
contents.contains("Filesystem Watcher Architecture"),
|
||||
@@ -627,15 +647,10 @@ mod tests {
|
||||
|
||||
let spike_id = create_spike_file(tmp.path(), "My Spike", None, &[]).unwrap();
|
||||
assert!(
|
||||
spike_id.ends_with("_spike_my_spike"),
|
||||
"expected ID to end with _spike_my_spike, got: {spike_id}"
|
||||
spike_id.chars().all(|c| c.is_ascii_digit()),
|
||||
"spike ID must be numeric-only, got: {spike_id}"
|
||||
);
|
||||
let num: u32 = spike_id
|
||||
.chars()
|
||||
.take_while(|c| c.is_ascii_digit())
|
||||
.collect::<String>()
|
||||
.parse()
|
||||
.unwrap();
|
||||
let num: u32 = spike_id.parse().unwrap();
|
||||
assert!(
|
||||
num >= 7051,
|
||||
"expected spike number >= 7051, got: {spike_id}"
|
||||
|
||||
Reference in New Issue
Block a user