Story 53: Add QA agent role with request_qa MCP tool

- Add `qa` agent entry to `.story_kit/project.toml` with a detailed
  prompt covering code quality scan, test verification, manual testing
  support, and structured report generation
- Add `move_story_to_qa` function in `agents.rs` that moves a work item
  from `work/2_current/` to `work/3_qa/` and auto-commits (idempotent)
- Add `request_qa` MCP tool in `mcp.rs` that moves the story to
  `work/3_qa/` and starts the QA agent on the existing worktree
- Add unit tests for `move_story_to_qa` (moves, idempotent, error cases)
- Update `tools_list_returns_all_tools` test to expect 27 tools

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Dave
2026-02-20 17:44:06 +00:00
parent 4a726e74c0
commit 122f481ab9
3 changed files with 230 additions and 2 deletions

View File

@@ -1,4 +1,4 @@
use crate::agents::{close_bug_to_archive, move_story_to_archived, move_story_to_merge};
use crate::agents::{close_bug_to_archive, move_story_to_archived, move_story_to_merge, move_story_to_qa};
use crate::config::ProjectConfig;
use crate::http::context::AppContext;
use crate::http::settings::get_editor_command_from_store;
@@ -742,6 +742,24 @@ fn handle_tools_list(id: Option<Value>) -> JsonRpcResponse {
},
"required": ["story_id"]
}
},
{
"name": "request_qa",
"description": "Trigger QA review of a completed story worktree: moves the item from work/2_current/ to work/3_qa/ and starts the qa agent to run quality gates, tests, and generate a manual testing plan.",
"inputSchema": {
"type": "object",
"properties": {
"story_id": {
"type": "string",
"description": "Story identifier (e.g. '53_story_qa_agent_role')"
},
"agent_name": {
"type": "string",
"description": "Agent name to use for QA (defaults to 'qa')"
}
},
"required": ["story_id"]
}
}
]
}),
@@ -797,6 +815,8 @@ async fn handle_tools_call(
// Mergemaster tools
"merge_agent_work" => tool_merge_agent_work(&args, ctx).await,
"move_story_to_merge" => tool_move_story_to_merge(&args, ctx),
// QA tools
"request_qa" => tool_request_qa(&args, ctx).await,
_ => Err(format!("Unknown tool: {tool_name}")),
};
@@ -1360,6 +1380,42 @@ fn tool_move_story_to_merge(args: &Value, ctx: &AppContext) -> Result<String, St
))
}
// ── QA tool implementations ───────────────────────────────────────
async fn tool_request_qa(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 agent_name = args
.get("agent_name")
.and_then(|v| v.as_str())
.unwrap_or("qa");
let project_root = ctx.agents.get_project_root(&ctx.state)?;
// Move story from work/2_current/ to work/3_qa/
move_story_to_qa(&project_root, story_id)?;
// Start the QA agent on the story worktree
let info = ctx
.agents
.start_agent(&project_root, story_id, Some(agent_name))
.await?;
serde_json::to_string_pretty(&json!({
"story_id": info.story_id,
"agent_name": info.agent_name,
"status": info.status.to_string(),
"worktree_path": info.worktree_path,
"message": format!(
"Story '{story_id}' moved to work/3_qa/ and QA agent '{}' started.",
info.agent_name
),
}))
.map_err(|e| format!("Serialization error: {e}"))
}
/// Run `git log <base>..HEAD --oneline` in the worktree and return the commit
/// summaries, or `None` if git is unavailable or there are no new commits.
async fn get_worktree_commits(worktree_path: &str, base_branch: &str) -> Option<Vec<String>> {
@@ -1519,7 +1575,8 @@ mod tests {
assert!(names.contains(&"close_bug"));
assert!(names.contains(&"merge_agent_work"));
assert!(names.contains(&"move_story_to_merge"));
assert_eq!(tools.len(), 26);
assert!(names.contains(&"request_qa"));
assert_eq!(tools.len(), 27);
}
#[test]