huskies: merge 1075

This commit is contained in:
dave
2026-05-15 00:42:19 +00:00
parent 6530eeab6d
commit eac5763e03
2 changed files with 84 additions and 18 deletions
+83 -17
View File
@@ -39,13 +39,17 @@ pub(crate) fn tool_get_pipeline_status(ctx: &AppContext) -> Result<String, Strin
let state = load_pipeline_state(ctx)?;
let running_merges = ctx.services.agents.list_running_merges()?;
fn slim_name(name: &str) -> &str {
crate::chat::util::truncate_at_char_boundary(name, 120)
}
fn map_items(items: &[crate::http::workflow::UpcomingStory], stage: &str) -> Vec<Value> {
items
.iter()
.map(|s| {
let mut item = json!({
"story_id": s.story_id,
"name": s.name,
"name": slim_name(&s.name),
"stage": stage,
"agent": s.agent.as_ref().map(|a| json!({
"agent_name": a.agent_name,
@@ -53,20 +57,12 @@ pub(crate) fn tool_get_pipeline_status(ctx: &AppContext) -> Result<String, Strin
"status": a.status,
})),
});
// Include blocked/retry_count when present so callers can
// identify stories stuck in the pipeline.
if let Some(true) = s.blocked {
item["blocked"] = json!(true);
}
if let Some(rc) = s.retry_count {
item["retry_count"] = json!(rc);
}
if let Some(ref mf) = s.merge_failure {
item["merge_failure"] = json!(mf);
}
if let Some(ref epic_id) = s.epic_id {
item["epic_id"] = json!(epic_id);
}
item
})
.collect()
@@ -81,19 +77,13 @@ pub(crate) fn tool_get_pipeline_status(ctx: &AppContext) -> Result<String, Strin
let backlog: Vec<Value> = state
.backlog
.iter()
.map(|s| {
let mut item = json!({ "story_id": s.story_id, "name": s.name });
if let Some(ref epic_id) = s.epic_id {
item["epic_id"] = json!(epic_id);
}
item
})
.map(|s| json!({ "story_id": s.story_id, "name": slim_name(&s.name) }))
.collect();
let archived: Vec<Value> = state
.archived
.iter()
.map(|s| json!({ "story_id": s.story_id, "name": s.name, "stage": "archived" }))
.map(|s| json!({ "story_id": s.story_id, "name": slim_name(&s.name), "stage": "archived" }))
.collect();
serde_json::to_string_pretty(&json!({
@@ -248,6 +238,82 @@ mod tests {
assert_eq!(item["valid"], true);
}
#[test]
fn pipeline_status_50_items_under_10kb() {
crate::db::ensure_content_store();
let stages = [
("1_backlog", "backlog"),
("2_current", "current"),
("3_qa", "qa"),
("4_merge", "merge"),
("5_done", "done"),
];
for (i, (dir, _)) in stages.iter().enumerate() {
for j in 0..10 {
let id = format!("99{i}{j}0_story_size_test");
let name = format!("Pipeline Size Test Story {i}-{j}");
crate::db::write_item_with_content(
&id,
dir,
&format!("---\nname: \"{name}\"\n---\n"),
crate::db::ItemMeta {
name: Some(name),
..Default::default()
},
);
}
}
let tmp = tempfile::tempdir().unwrap();
let ctx = test_ctx(tmp.path());
let result = tool_get_pipeline_status(&ctx).unwrap();
assert!(
result.len() < 10 * 1024,
"50-item response must be under 10 KB; got {} bytes",
result.len()
);
}
#[test]
fn pipeline_status_per_item_under_500_bytes() {
crate::db::ensure_content_store();
// Insert one item per active stage with a moderately long name.
let stages = [
("2_current", "9995_story_peritem_current"),
("3_qa", "9996_story_peritem_qa"),
("4_merge", "9997_story_peritem_merge"),
("5_done", "9998_story_peritem_done"),
];
for (dir, id) in &stages {
let name = "A Reasonably Named Story For Size Testing";
crate::db::write_item_with_content(
id,
dir,
&format!("---\nname: \"{name}\"\n---\n"),
crate::db::ItemMeta {
name: Some(name.to_string()),
..Default::default()
},
);
}
let tmp = tempfile::tempdir().unwrap();
let ctx = test_ctx(tmp.path());
let result = tool_get_pipeline_status(&ctx).unwrap();
let parsed: Value = serde_json::from_str(&result).unwrap();
let active = parsed["active"].as_array().unwrap();
for item in active {
if stages.iter().any(|(_, id)| item["story_id"] == *id) {
let item_json = serde_json::to_string(item).unwrap();
assert!(
item_json.len() < 500,
"per-item payload must be under 500 bytes; story_id={} got {} bytes: {}",
item["story_id"],
item_json.len(),
item_json
);
}
}
}
#[test]
fn tool_validate_stories_with_invalid_front_matter() {
let tmp = tempfile::tempdir().unwrap();
@@ -574,7 +574,7 @@ pub(super) fn story_tools() -> Vec<Value> {
}),
json!({
"name": "get_pipeline_status",
"description": "Return a structured snapshot of the full work item pipeline. Includes all active stages (current, qa, merge, done) with each item's stage, name, and assigned agent. Also includes upcoming backlog items.",
"description": "Return a structured snapshot of the full work item pipeline. Each item includes only slim fields: story_id, name (capped at 120 chars), stage, agent (with agent_name/model/status), and optional boolean flags blocked and retry_count. Active stages (current, qa, merge, done) appear in the 'active' array; backlog items in 'backlog'. For full story details, use status(story_id) or dump_crdt.",
"inputSchema": {
"type": "object",
"properties": {}