huskies: merge 1237 bug status resolves the story number against a different project than show

This commit is contained in:
Huskies Agent
2026-07-20 14:30:23 +00:00
parent 7ac5bd196f
commit f7b7f21e88
2 changed files with 69 additions and 29 deletions
+7 -3
View File
@@ -102,10 +102,14 @@ pub(super) fn handle_show(ctx: &CommandContext) -> Option<String> {
}
};
// `content` comes from the CRDT / content store. If unavailable, report
// it rather than silently reading a stale on-disk copy.
// `content` must be present for any story the shared lookup returns —
// story 1222 made the content store retain a story's body indefinitely,
// so a missing body here is a content-store bug, not an expected state.
// Fail loudly instead of masking it with a placeholder message.
let text = content.unwrap_or_else(|| {
format!("Story {story_id} found in pipeline but its content is unavailable.")
panic!(
"story {story_id} found by find_story_by_number but has no content in the content store"
)
});
// Strip front matter block from the displayed body; source the metadata
+62 -26
View File
@@ -19,46 +19,34 @@ use std::process::Command;
const MAX_DIRTY_FILES_SHOWN: usize = 20;
/// Handle `{bot_name} status {number}`.
///
/// Resolves the numeric prefix via the shared [`crate::chat::lookup::find_story_by_number`]
/// lookup against the room's active project — the same lookup `show` uses —
/// rather than a separate pipeline-item scan, so `status <n>` and `show <n>`
/// always agree on which story a bare number refers to (story 1237).
pub(super) fn handle_triage(ctx: &CommandContext) -> Option<String> {
let num_str = ctx.args.trim();
if num_str.is_empty() || !num_str.chars().all(|c| c.is_ascii_digit()) {
return None;
}
match find_story_by_number(num_str) {
Some((story_id, item)) => Some(build_triage_dump(ctx, &story_id, &item, num_str)),
match crate::chat::lookup::find_story_by_number(ctx.effective_root(), num_str) {
Some((story_id, stage_dir, _path, content)) => Some(build_triage_dump(
ctx, &story_id, &stage_dir, content, num_str,
)),
None => Some(format!("Story **{num_str}** not found in the pipeline.")),
}
}
/// Find a pipeline item whose numeric prefix matches `num_str` by querying the
/// CRDT state. Returns `(story_id, PipelineItem)` for the first match.
fn find_story_by_number(num_str: &str) -> Option<(String, crate::pipeline_state::PipelineItem)> {
let items = crate::pipeline_state::read_all_typed();
for item in items {
let file_num = item
.story_id
.0
.split('_')
.next()
.filter(|s| !s.is_empty() && s.chars().all(|c| c.is_ascii_digit()))
.unwrap_or("");
if file_num == num_str {
let story_id = item.story_id.0.clone();
return Some((story_id, item));
}
}
None
}
/// Build the full triage dump for a story.
fn build_triage_dump(
ctx: &CommandContext,
story_id: &str,
item: &crate::pipeline_state::PipelineItem,
fallback_stage_dir: &str,
content: Option<String>,
num_str: &str,
) -> String {
let contents = match crate::db::read_content(crate::db::ContentKey::Story(story_id)) {
let contents = match content {
Some(c) => c,
None => return format!("Story {num_str}: content not found in content store."),
};
@@ -72,8 +60,18 @@ fn build_triage_dump(
// ---- Header ----
out.push_str(&format!("## Story {num_str}{name}\n"));
let stage_name = crate::pipeline_state::stage_label(&item.stage);
let dir_name = crate::pipeline_state::stage_dir_name(&item.stage);
// `fallback_stage_dir` covers the rare case where the shared lookup found
// the story via the content store but it hasn't synced into the CRDT yet.
let (stage_name, dir_name) = match crdt_item.as_ref() {
Some(w) => (
crate::pipeline_state::stage_label(w.stage()).to_string(),
crate::pipeline_state::stage_dir_name(w.stage()).to_string(),
),
None => (
fallback_stage_dir.to_string(),
fallback_stage_dir.to_string(),
),
};
out.push_str(&format!("**Stage:** {stage_name} (`{dir_name}`)\n\n"));
// ---- CRDT metadata ----
@@ -356,6 +354,44 @@ mod tests {
);
}
/// Story 1237, AC1 + AC3: `status <n>` and `show <n>` must resolve a bare
/// story number to the same story in the same room — both now go through
/// the shared `chat::lookup::find_story_by_number` lookup instead of
/// `status`/`triage` running its own separate pipeline-item scan.
#[test]
fn status_and_show_resolve_to_same_story_in_same_room() {
let tmp = tempfile::TempDir::new().unwrap();
write_story_file(
tmp.path(),
"2_current",
"9910_story_shared_lookup.md",
"---\nname: Shared Lookup Test\n---\n\n# Story\n\nBody text for consistency check.",
Some("Shared Lookup Test"),
);
let status_output = status_triage_cmd(tmp.path(), "9910").unwrap();
let services =
crate::services::Services::new_test(tmp.path().to_path_buf(), "Timmy".to_string());
let room_id = "!test:example.com".to_string();
let dispatch = CommandDispatch {
services: &services,
project_root: &services.project_root,
bot_user_id: "@timmy:homeserver.local",
room_id: &room_id,
};
let show_output = try_handle_command(&dispatch, "@timmy show 9910").unwrap();
assert!(
status_output.contains("Shared Lookup Test"),
"status should resolve story 9910: {status_output}"
);
assert!(
show_output.contains("Body text for consistency check."),
"show should resolve story 9910: {show_output}"
);
}
#[test]
fn whatsup_works_for_story_in_backlog() {
let tmp = tempfile::TempDir::new().unwrap();