story-kit: merge 254_story_add_refactor_work_item_type

This commit is contained in:
Dave
2026-03-17 00:37:45 +00:00
parent 3553f59078
commit 2fae9066e2
3 changed files with 202 additions and 6 deletions

View File

@@ -6,9 +6,9 @@ use crate::slog_warn;
use crate::http::context::AppContext;
use crate::http::settings::get_editor_command_from_store;
use crate::http::workflow::{
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,
add_criterion_to_file, check_criterion_in_file, create_bug_file, create_refactor_file,
create_spike_file, create_story_file, list_bug_files, list_refactor_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, write_merge_failure};
@@ -719,6 +719,37 @@ fn handle_tools_list(id: Option<Value>) -> JsonRpcResponse {
"properties": {}
}
},
{
"name": "create_refactor",
"description": "Create a refactor work item in work/1_upcoming/ with a deterministic filename and YAML front matter. Returns the refactor_id.",
"inputSchema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Short human-readable refactor name"
},
"description": {
"type": "string",
"description": "Optional description of the desired state after refactoring"
},
"acceptance_criteria": {
"type": "array",
"items": { "type": "string" },
"description": "Optional list of acceptance criteria"
}
},
"required": ["name"]
}
},
{
"name": "list_refactors",
"description": "List all open refactors in work/1_upcoming/ matching the _refactor_ naming convention.",
"inputSchema": {
"type": "object",
"properties": {}
}
},
{
"name": "close_bug",
"description": "Archive a bug from work/2_current/ or work/1_upcoming/ to work/5_done/ and auto-commit to master.",
@@ -896,6 +927,9 @@ async fn handle_tools_call(
"create_bug" => tool_create_bug(&args, ctx),
"list_bugs" => tool_list_bugs(ctx),
"close_bug" => tool_close_bug(&args, ctx),
// Refactor lifecycle tools
"create_refactor" => tool_create_refactor(&args, ctx),
"list_refactors" => tool_list_refactors(ctx),
// Mergemaster tools
"merge_agent_work" => tool_merge_agent_work(&args, ctx).await,
"move_story_to_merge" => tool_move_story_to_merge(&args, ctx).await,
@@ -1582,6 +1616,39 @@ fn tool_close_bug(args: &Value, ctx: &AppContext) -> Result<String, String> {
))
}
// ── Refactor lifecycle tool implementations ───────────────────────
fn tool_create_refactor(args: &Value, ctx: &AppContext) -> Result<String, String> {
let name = args
.get("name")
.and_then(|v| v.as_str())
.ok_or("Missing required argument: name")?;
let description = args.get("description").and_then(|v| v.as_str());
let acceptance_criteria: Option<Vec<String>> = args
.get("acceptance_criteria")
.and_then(|v| serde_json::from_value(v.clone()).ok());
let root = ctx.state.get_project_root()?;
let refactor_id = create_refactor_file(
&root,
name,
description,
acceptance_criteria.as_deref(),
)?;
Ok(format!("Created refactor: {refactor_id}"))
}
fn tool_list_refactors(ctx: &AppContext) -> Result<String, String> {
let root = ctx.state.get_project_root()?;
let refactors = list_refactor_files(&root)?;
serde_json::to_string_pretty(&json!(refactors
.iter()
.map(|(id, name)| json!({ "refactor_id": id, "name": name }))
.collect::<Vec<_>>()))
.map_err(|e| format!("Serialization error: {e}"))
}
// ── Mergemaster tool implementations ─────────────────────────────
async fn tool_merge_agent_work(args: &Value, ctx: &AppContext) -> Result<String, String> {
@@ -2077,13 +2144,15 @@ mod tests {
assert!(names.contains(&"create_bug"));
assert!(names.contains(&"list_bugs"));
assert!(names.contains(&"close_bug"));
assert!(names.contains(&"create_refactor"));
assert!(names.contains(&"list_refactors"));
assert!(names.contains(&"merge_agent_work"));
assert!(names.contains(&"move_story_to_merge"));
assert!(names.contains(&"report_merge_failure"));
assert!(names.contains(&"request_qa"));
assert!(names.contains(&"get_server_logs"));
assert!(names.contains(&"prompt_permission"));
assert_eq!(tools.len(), 31);
assert_eq!(tools.len(), 33);
}
#[test]