story-kit: merge 177_bug_no_mcp_tool_to_edit_story_acceptance_criteria

This commit is contained in:
Dave
2026-02-25 11:34:37 +00:00
parent e419c198a6
commit aa423cae22
2 changed files with 342 additions and 3 deletions

View File

@@ -5,8 +5,9 @@ use crate::slog_warn;
use crate::http::context::AppContext;
use crate::http::settings::get_editor_command_from_store;
use crate::http::workflow::{
check_criterion_in_file, create_bug_file, create_spike_file, create_story_file, list_bug_files,
load_upcoming_stories, validate_story_dirs,
add_criterion_to_file, check_criterion_in_file, create_bug_file, create_spike_file,
create_story_file, list_bug_files, load_upcoming_stories, update_story_in_file,
validate_story_dirs,
};
use crate::worktree;
use crate::io::story_metadata::{parse_front_matter, parse_unchecked_todos};
@@ -616,6 +617,46 @@ fn handle_tools_list(id: Option<Value>) -> JsonRpcResponse {
"required": ["story_id", "criterion_index"]
}
},
{
"name": "add_criterion",
"description": "Add an acceptance criterion to an existing story file. Appends '- [ ] {criterion}' after the last existing criterion in the '## Acceptance Criteria' section. Auto-commits via the filesystem watcher.",
"inputSchema": {
"type": "object",
"properties": {
"story_id": {
"type": "string",
"description": "Story identifier (filename stem, e.g. '28_my_story')"
},
"criterion": {
"type": "string",
"description": "The acceptance criterion text to add (without the '- [ ] ' prefix)"
}
},
"required": ["story_id", "criterion"]
}
},
{
"name": "update_story",
"description": "Update the user story text and/or description of an existing story file. Replaces the content of the '## User Story' and/or '## Description' section in place. Auto-commits via the filesystem watcher.",
"inputSchema": {
"type": "object",
"properties": {
"story_id": {
"type": "string",
"description": "Story identifier (filename stem, e.g. '28_my_story')"
},
"user_story": {
"type": "string",
"description": "New user story text to replace the '## User Story' section content"
},
"description": {
"type": "string",
"description": "New description text to replace the '## Description' section content"
}
},
"required": ["story_id"]
}
},
{
"name": "create_spike",
"description": "Create a spike file in .story_kit/work/1_upcoming/ with a deterministic filename and YAML front matter. Returns the spike_id.",
@@ -828,6 +869,8 @@ async fn handle_tools_call(
"accept_story" => tool_accept_story(&args, ctx),
// Story mutation tools (auto-commit to master)
"check_criterion" => tool_check_criterion(&args, ctx),
"add_criterion" => tool_add_criterion(&args, ctx),
"update_story" => tool_update_story(&args, ctx),
// Spike lifecycle tools
"create_spike" => tool_create_spike(&args, ctx),
// Bug lifecycle tools
@@ -1378,6 +1421,38 @@ fn tool_check_criterion(args: &Value, ctx: &AppContext) -> Result<String, String
))
}
fn tool_add_criterion(args: &Value, ctx: &AppContext) -> Result<String, String> {
let story_id = args
.get("story_id")
.and_then(|v| v.as_str())
.ok_or("Missing required argument: story_id")?;
let criterion = args
.get("criterion")
.and_then(|v| v.as_str())
.ok_or("Missing required argument: criterion")?;
let root = ctx.state.get_project_root()?;
add_criterion_to_file(&root, story_id, criterion)?;
Ok(format!(
"Added criterion to story '{story_id}': - [ ] {criterion}"
))
}
fn tool_update_story(args: &Value, ctx: &AppContext) -> Result<String, String> {
let story_id = args
.get("story_id")
.and_then(|v| v.as_str())
.ok_or("Missing required argument: story_id")?;
let user_story = args.get("user_story").and_then(|v| v.as_str());
let description = args.get("description").and_then(|v| v.as_str());
let root = ctx.state.get_project_root()?;
update_story_in_file(&root, story_id, user_story, description)?;
Ok(format!("Updated story '{story_id}'."))
}
// ── Spike lifecycle tool implementations ─────────────────────────
fn tool_create_spike(args: &Value, ctx: &AppContext) -> Result<String, String> {
@@ -1795,6 +1870,8 @@ mod tests {
assert!(!names.contains(&"report_completion"));
assert!(names.contains(&"accept_story"));
assert!(names.contains(&"check_criterion"));
assert!(names.contains(&"add_criterion"));
assert!(names.contains(&"update_story"));
assert!(names.contains(&"create_spike"));
assert!(names.contains(&"create_bug"));
assert!(names.contains(&"list_bugs"));
@@ -1804,7 +1881,7 @@ mod tests {
assert!(names.contains(&"request_qa"));
assert!(names.contains(&"get_server_logs"));
assert!(names.contains(&"prompt_permission"));
assert_eq!(tools.len(), 28);
assert_eq!(tools.len(), 30);
}
#[test]