Adding show story mcp
This commit is contained in:
@@ -150,37 +150,40 @@ async fn git_branch(dir: &Path) -> Option<String> {
|
||||
.flatten()
|
||||
}
|
||||
|
||||
pub(super) async fn tool_status(args: &Value, ctx: &AppContext) -> Result<String, String> {
|
||||
let story_id = args
|
||||
pub(super) async fn tool_show(args: &Value, ctx: &AppContext) -> Result<String, String> {
|
||||
let raw_id = args
|
||||
.get("story_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or("Missing required argument: story_id")?;
|
||||
|
||||
let root = ctx.state.get_project_root()?;
|
||||
|
||||
// Read from CRDT/DB content store — verify the item is in coding.
|
||||
// Resolve numeric prefix (e.g. "5") to full story_id via find_story_by_number.
|
||||
let (story_id, contents) = if raw_id.chars().all(|c| c.is_ascii_digit()) {
|
||||
let (sid, _, _, content) = crate::chat::lookup::find_story_by_number(&root, raw_id)
|
||||
.ok_or_else(|| {
|
||||
format!("No work item with number '{raw_id}' found in any pipeline stage.")
|
||||
})?;
|
||||
let body = content.ok_or_else(|| {
|
||||
format!("Work item '{sid}' found in pipeline but its content is unavailable.")
|
||||
})?;
|
||||
(sid, body)
|
||||
} else {
|
||||
let body = crate::db::read_content(crate::db::ContentKey::Story(raw_id))
|
||||
.ok_or_else(|| format!("Work item '{raw_id}' not found in any pipeline stage."))?;
|
||||
(raw_id.to_string(), body)
|
||||
};
|
||||
|
||||
let story_id = story_id.as_str();
|
||||
|
||||
let typed_item = crate::pipeline_state::read_typed(story_id)
|
||||
.map_err(|e| format!("Failed to read pipeline state: {e}"))?
|
||||
.ok_or_else(|| format!(
|
||||
"Story '{story_id}' not found in coding stage. Check the story_id and ensure it is in the current stage."
|
||||
))?;
|
||||
|
||||
if !matches!(
|
||||
typed_item.stage,
|
||||
crate::pipeline_state::Stage::Coding { .. }
|
||||
) {
|
||||
return Err(format!(
|
||||
"Story '{story_id}' not found in coding stage. Check the story_id and ensure it is in the current stage."
|
||||
));
|
||||
}
|
||||
|
||||
let contents = crate::db::read_content(crate::db::ContentKey::Story(story_id))
|
||||
.ok_or_else(|| format!("Story '{story_id}' has no content in the content store."))?;
|
||||
.map_err(|e| format!("Failed to read pipeline state: {e}"))?;
|
||||
|
||||
// --- Metadata (story 929: CRDT-first, yaml_residue marks gaps) ---
|
||||
let mut front_matter = serde_json::Map::new();
|
||||
if let Some(view) = crate::crdt_state::read_item(story_id) {
|
||||
front_matter.insert("name".to_string(), json!(view.name()));
|
||||
front_matter.insert("stage".to_string(), json!(view.stage().dir_name()));
|
||||
if let Some(agent) = view.agent() {
|
||||
front_matter.insert("agent".to_string(), json!(agent));
|
||||
}
|
||||
@@ -195,14 +198,13 @@ pub(super) async fn tool_status(args: &Value, ctx: &AppContext) -> Result<String
|
||||
if !deps.is_empty() {
|
||||
front_matter.insert("depends_on".to_string(), json!(deps));
|
||||
}
|
||||
// Story 1088: origin tracking.
|
||||
let origin_str = view.origin().unwrap_or("unknown");
|
||||
front_matter.insert("origin".to_string(), json!(origin_str));
|
||||
let stage_claim = match &typed_item.stage {
|
||||
let stage_claim = typed_item.as_ref().and_then(|t| match &t.stage {
|
||||
crate::pipeline_state::Stage::Coding { claim, .. } => claim.as_ref(),
|
||||
crate::pipeline_state::Stage::Merge { claim, .. } => claim.as_ref(),
|
||||
_ => None,
|
||||
};
|
||||
});
|
||||
if let Some(claim) = stage_claim {
|
||||
front_matter.insert("claimed_by".to_string(), json!(claim.agent.0.as_str()));
|
||||
front_matter.insert(
|
||||
@@ -212,7 +214,6 @@ pub(super) async fn tool_status(args: &Value, ctx: &AppContext) -> Result<String
|
||||
}
|
||||
}
|
||||
|
||||
// Merge-failure detail lives on the MergeJob CRDT entry, not on WorkItem.
|
||||
if let Some(job) = crate::crdt_state::read_merge_job(story_id)
|
||||
&& let Some(mf) = job.error
|
||||
{
|
||||
@@ -343,16 +344,16 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn tool_status_returns_error_for_missing_story() {
|
||||
async fn tool_show_returns_error_for_missing_story() {
|
||||
let tmp = tempdir().unwrap();
|
||||
let ctx = crate::http::context::AppContext::new_test(tmp.path().to_path_buf());
|
||||
let result = tool_status(&json!({"story_id": "999_story_nonexistent"}), &ctx).await;
|
||||
let result = tool_show(&json!({"story_id": "999_story_nonexistent"}), &ctx).await;
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("not found in coding stage"));
|
||||
assert!(result.unwrap_err().contains("not found"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn tool_status_returns_retry_count_and_depends_on() {
|
||||
async fn tool_show_returns_retry_count_and_depends_on() {
|
||||
let tmp = tempdir().unwrap();
|
||||
|
||||
crate::crdt_state::init_for_test();
|
||||
@@ -368,7 +369,7 @@ mod tests {
|
||||
crate::crdt_state::set_depends_on("9887_story_blocked_test", &[100, 200]);
|
||||
|
||||
let ctx = crate::http::context::AppContext::new_test(tmp.path().to_path_buf());
|
||||
let result = tool_status(&json!({"story_id": "9887_story_blocked_test"}), &ctx)
|
||||
let result = tool_show(&json!({"story_id": "9887_story_blocked_test"}), &ctx)
|
||||
.await
|
||||
.unwrap();
|
||||
let parsed: serde_json::Value = serde_json::from_str(&result).unwrap();
|
||||
@@ -381,7 +382,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn tool_status_returns_story_data() {
|
||||
async fn tool_show_returns_story_data() {
|
||||
let tmp = tempdir().unwrap();
|
||||
|
||||
crate::db::ensure_content_store();
|
||||
@@ -398,7 +399,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let ctx = crate::http::context::AppContext::new_test(tmp.path().to_path_buf());
|
||||
let result = tool_status(&json!({"story_id": "9886_story_status_test"}), &ctx)
|
||||
let result = tool_show(&json!({"story_id": "9886_story_status_test"}), &ctx)
|
||||
.await
|
||||
.unwrap();
|
||||
let parsed: serde_json::Value = serde_json::from_str(&result).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user