story-kit: merge 177_bug_no_mcp_tool_to_edit_story_acceptance_criteria
This commit is contained in:
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user