storkit: merge 378_story_status_command_shows_work_item_type_story_bug_spike_refactor_next_to_each_item

This commit is contained in:
dave
2026-03-23 18:40:15 +00:00
parent f10ea1ecf2
commit 4a94158ef2

View File

@@ -16,23 +16,36 @@ pub(super) fn handle_status(ctx: &CommandContext) -> Option<String> {
/// Format a short display label for a work item. /// Format a short display label for a work item.
/// ///
/// Extracts the leading numeric ID from the file stem (e.g. `"293"` from /// Extracts the leading numeric ID and optional type tag from the file stem
/// `"293_story_register_all_bot_commands"`) and combines it with the human- /// (e.g. `"293"` and `"story"` from `"293_story_register_all_bot_commands"`)
/// readable name from the front matter when available. /// and combines them with the human-readable name from the front matter when
/// available. Known types (`story`, `bug`, `spike`, `refactor`) are shown as
/// bracketed labels; unknown or missing types are omitted silently.
/// ///
/// Examples: /// Examples:
/// - `("293_story_foo", Some("Register all bot commands"))` → `"293 — Register all bot commands"` /// - `("293_story_foo", Some("Register all bot commands"))` → `"293 [story] — Register all bot commands"`
/// - `("293_story_foo", None)` → `"293"` /// - `("375_bug_foo", None)` → `"375 [bug]"`
/// - `("293_story_foo", None)` → `"293 [story]"`
/// - `("no_number_here", None)` → `"no_number_here"` /// - `("no_number_here", None)` → `"no_number_here"`
pub(super) fn story_short_label(stem: &str, name: Option<&str>) -> String { pub(super) fn story_short_label(stem: &str, name: Option<&str>) -> String {
let number = stem let mut parts = stem.splitn(3, '_');
.split('_') let first = parts.next().unwrap_or(stem);
.next() let (number, type_label) = if !first.is_empty() && first.chars().all(|c| c.is_ascii_digit()) {
.filter(|s| !s.is_empty() && s.chars().all(|c| c.is_ascii_digit())) let t = parts.next().and_then(|t| match t {
.unwrap_or(stem); "story" | "bug" | "spike" | "refactor" => Some(t),
match name { _ => None,
Some(n) => format!("{number}{n}"), });
(first, t)
} else {
(stem, None)
};
let prefix = match type_label {
Some(t) => format!("{number} [{t}]"),
None => number.to_string(), None => number.to_string(),
};
match name {
Some(n) => format!("{prefix}{n}"),
None => prefix,
} }
} }
@@ -200,13 +213,13 @@ mod tests {
#[test] #[test]
fn short_label_extracts_number_and_name() { fn short_label_extracts_number_and_name() {
let label = story_short_label("293_story_register_all_bot_commands", Some("Register all bot commands")); let label = story_short_label("293_story_register_all_bot_commands", Some("Register all bot commands"));
assert_eq!(label, "293 — Register all bot commands"); assert_eq!(label, "293 [story] — Register all bot commands");
} }
#[test] #[test]
fn short_label_number_only_when_no_name() { fn short_label_number_only_when_no_name() {
let label = story_short_label("297_story_improve_bot_status_command_formatting", None); let label = story_short_label("297_story_improve_bot_status_command_formatting", None);
assert_eq!(label, "297"); assert_eq!(label, "297 [story]");
} }
#[test] #[test]
@@ -224,6 +237,37 @@ mod tests {
); );
} }
#[test]
fn short_label_shows_bug_type() {
let label = story_short_label("375_bug_default_project_toml", Some("Default project.toml issue"));
assert_eq!(label, "375 [bug] — Default project.toml issue");
}
#[test]
fn short_label_shows_spike_type() {
let label = story_short_label("61_spike_filesystem_watcher_architecture", Some("Filesystem watcher architecture"));
assert_eq!(label, "61 [spike] — Filesystem watcher architecture");
}
#[test]
fn short_label_shows_refactor_type() {
let label = story_short_label("260_refactor_upgrade_libsqlite3_sys", Some("Upgrade libsqlite3-sys"));
assert_eq!(label, "260 [refactor] — Upgrade libsqlite3-sys");
}
#[test]
fn short_label_omits_unknown_type() {
let label = story_short_label("42_task_do_something", Some("Do something"));
assert_eq!(label, "42 — Do something");
}
#[test]
fn short_label_no_type_when_only_id() {
// Stem with only a numeric ID and no type segment
let label = story_short_label("42", Some("Some item"));
assert_eq!(label, "42 — Some item");
}
// -- build_pipeline_status formatting ----------------------------------- // -- build_pipeline_status formatting -----------------------------------
#[test] #[test]
@@ -248,8 +292,8 @@ mod tests {
"output must not show full filename stem: {output}" "output must not show full filename stem: {output}"
); );
assert!( assert!(
output.contains("293 — Register all bot commands"), output.contains("293 [story] — Register all bot commands"),
"output must show number and title: {output}" "output must show number, type, and title: {output}"
); );
} }
@@ -288,7 +332,7 @@ mod tests {
let output = build_pipeline_status(tmp.path(), &agents); let output = build_pipeline_status(tmp.path(), &agents);
assert!( assert!(
output.contains("293 — Register all bot commands — $0.29"), output.contains("293 [story] — Register all bot commands — $0.29"),
"output must show cost next to story: {output}" "output must show cost next to story: {output}"
); );
} }
@@ -351,7 +395,7 @@ mod tests {
let output = build_pipeline_status(tmp.path(), &agents); let output = build_pipeline_status(tmp.path(), &agents);
assert!( assert!(
output.contains("293 — Register all bot commands — $0.29"), output.contains("293 [story] — Register all bot commands — $0.29"),
"output must show aggregated cost: {output}" "output must show aggregated cost: {output}"
); );
} }