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 state = load_pipeline_state(ctx)?;
|
||||||
let running_merges = ctx.services.agents.list_running_merges()?;
|
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> {
|
fn map_items(items: &[crate::http::workflow::UpcomingStory], stage: &str) -> Vec<Value> {
|
||||||
items
|
items
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| {
|
.map(|s| {
|
||||||
let mut item = json!({
|
let mut item = json!({
|
||||||
"story_id": s.story_id,
|
"story_id": s.story_id,
|
||||||
"name": s.name,
|
"name": slim_name(&s.name),
|
||||||
"stage": stage,
|
"stage": stage,
|
||||||
"agent": s.agent.as_ref().map(|a| json!({
|
"agent": s.agent.as_ref().map(|a| json!({
|
||||||
"agent_name": a.agent_name,
|
"agent_name": a.agent_name,
|
||||||
@@ -53,20 +57,12 @@ pub(crate) fn tool_get_pipeline_status(ctx: &AppContext) -> Result<String, Strin
|
|||||||
"status": a.status,
|
"status": a.status,
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
// Include blocked/retry_count when present so callers can
|
|
||||||
// identify stories stuck in the pipeline.
|
|
||||||
if let Some(true) = s.blocked {
|
if let Some(true) = s.blocked {
|
||||||
item["blocked"] = json!(true);
|
item["blocked"] = json!(true);
|
||||||
}
|
}
|
||||||
if let Some(rc) = s.retry_count {
|
if let Some(rc) = s.retry_count {
|
||||||
item["retry_count"] = json!(rc);
|
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
|
item
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
@@ -81,19 +77,13 @@ pub(crate) fn tool_get_pipeline_status(ctx: &AppContext) -> Result<String, Strin
|
|||||||
let backlog: Vec<Value> = state
|
let backlog: Vec<Value> = state
|
||||||
.backlog
|
.backlog
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| {
|
.map(|s| json!({ "story_id": s.story_id, "name": slim_name(&s.name) }))
|
||||||
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
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let archived: Vec<Value> = state
|
let archived: Vec<Value> = state
|
||||||
.archived
|
.archived
|
||||||
.iter()
|
.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();
|
.collect();
|
||||||
|
|
||||||
serde_json::to_string_pretty(&json!({
|
serde_json::to_string_pretty(&json!({
|
||||||
@@ -248,6 +238,82 @@ mod tests {
|
|||||||
assert_eq!(item["valid"], true);
|
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]
|
#[test]
|
||||||
fn tool_validate_stories_with_invalid_front_matter() {
|
fn tool_validate_stories_with_invalid_front_matter() {
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
|||||||
@@ -574,7 +574,7 @@ pub(super) fn story_tools() -> Vec<Value> {
|
|||||||
}),
|
}),
|
||||||
json!({
|
json!({
|
||||||
"name": "get_pipeline_status",
|
"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": {
|
"inputSchema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {}
|
"properties": {}
|
||||||
|
|||||||
Reference in New Issue
Block a user