huskies: merge 1075
This commit is contained in:
@@ -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": {}
|
||||
|
||||
Reference in New Issue
Block a user