Refactor remaining tools to tagged enums

Refactored DecisionsTool, TechDebtTool, ManageCheckpointTool, HypothesesTool, AgentSignalsTool to use tagged enums for precise JSON schemas, and updated their handlers.
This commit is contained in:
Riz Ashraf committed 2026-10-10 10:23:58 +01:00
1 parent 8ff43487bc
commit 2cfaeda0cb
3 files changed
+438 -497

No files matched your search

+35 -68
View File
@@ -23,15 +23,12 @@ impl McpTool for TasksHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: TasksTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
match req.action {
TaskAction::Add => {
let title = req.title.ok_or_else(|| {
crate::error::AppError::Internal("Missing required parameter 'title' for action 'add'. Next step: Provide non-empty 'title' string in request and retry.".to_string())
})?;
let description = req.description.unwrap_or_default();
match req {
TasksTool::Add { title, description, parent_id, dependencies, git_branch, namespace, repo_name, priority, assigned_agent, verification_command } => {
let description = description.unwrap_or_default();
let now = crate::handlers::utils::now_secs();
let task_id = uuid::Uuid::new_v4().to_string();
let deps = req.dependencies.unwrap_or_default();
let deps = dependencies.unwrap_or_default();
let task = Task {
id: task_id.clone(),
@@ -40,16 +37,16 @@ impl McpTool for TasksHandler {
description,
created_at: now,
updated_at: now,
git_branch: req.git_branch,
parent_id: req.parent_id,
git_branch,
parent_id,
dependencies: deps,
acceptance_criteria: vec![],
expires_at: None,
namespace: req.namespace.unwrap_or_else(|| "default".to_string()),
repo_name: req.repo_name,
priority: req.priority,
assigned_agent: req.assigned_agent,
verification_command: req.verification_command,
namespace: namespace.unwrap_or_else(|| "default".to_string()),
repo_name,
priority,
assigned_agent,
verification_command,
};
let idx = state.get_search_index().await;
drop(idx.index_task(&task));
@@ -75,13 +72,7 @@ impl McpTool for TasksHandler {
});
Ok(format!("Task added with ID: {}", task_id))
}
TaskAction::Update => {
let id = req.id.ok_or_else(|| {
crate::error::AppError::Internal("Missing required parameter 'id' for action 'update'. Next step: Provide task 'id' string in request and retry.".to_string())
})?;
let status = req.status.ok_or_else(|| {
crate::error::AppError::Internal("Missing required parameter 'status' for action 'update'. Next step: Provide valid 'status' ('pending', 'active', 'in_progress', 'blocked', 'completed', or 'cancelled') in request and retry.".to_string())
})?;
TasksTool::Update { id, status } => {
let target_status = status.to_lowercase();
let mut found = false;
let mut blocked = false;
@@ -202,10 +193,7 @@ impl McpTool for TasksHandler {
))
}
}
TaskAction::Delete => {
let id = req.id.ok_or_else(|| {
crate::error::AppError::Internal("Missing required parameter 'id' for action 'delete'. Next step: Provide task 'id' string in request and retry.".to_string())
})?;
TasksTool::Delete { id } => {
let mut deleted_count = 0;
let mut actually_deleted = Vec::new();
state.project.tasks.modify(|tasks| {
@@ -267,19 +255,19 @@ impl McpTool for TasksHandler {
))
}
}
TaskAction::List => {
let level = req.summary_level.as_deref().unwrap_or("detailed");
let offset = req.offset.unwrap_or(0);
TasksTool::List { git_branch, status, summary_level, max_tokens, offset } => {
let level = summary_level.as_deref().unwrap_or("detailed");
let offset = offset.unwrap_or(0);
let data = state.project.tasks.read_with(|tasks| {
let matching: Vec<_> = tasks
.iter()
.filter(|t| {
let status_match = match req.status.as_deref() {
let status_match = match status.as_deref() {
Some("all") => true,
Some(s) => t.status.eq_ignore_ascii_case(s),
None => t.is_active(),
};
let branch_match = match &req.git_branch {
let branch_match = match &git_branch {
Some(branch) => t.git_branch.is_none() || t.git_branch.as_deref() == Some(branch.as_str()),
None => true,
};
@@ -297,7 +285,7 @@ impl McpTool for TasksHandler {
})
.collect();
let mut json_str = serde_yaml::to_string(&filtered)?;
if let Some(max_t) = req.max_tokens {
if let Some(max_t) = max_tokens {
let char_limit = max_t * 4;
while json_str.len() > char_limit && !filtered.is_empty() {
filtered.pop();
@@ -308,13 +296,7 @@ impl McpTool for TasksHandler {
})?;
Ok(data)
}
TaskAction::SetCriteria => {
let id = req.id.or(req.title.clone()).ok_or_else(|| {
crate::error::AppError::Internal("Missing required parameter 'id' or 'title' for action 'set_criteria'. Next step: Provide task 'id' string in request and retry.".to_string())
})?;
let criteria_list = req.criteria.ok_or_else(|| {
crate::error::AppError::Internal("Missing required parameter 'criteria' for action 'set_criteria'. Next step: Provide array of acceptance criteria strings in request and retry.".to_string())
})?;
TasksTool::SetCriteria { id, criteria: criteria_list } => {
let mut success = false;
let mut updated_task: Option<Task> = None;
state.project.tasks.modify(|tasks| {
@@ -355,11 +337,8 @@ impl McpTool for TasksHandler {
))
}
}
TaskAction::Verify => {
let id = req.id.ok_or_else(|| {
crate::error::AppError::Internal("Missing required parameter 'id' for action 'verify'. Next step: Provide task 'id' string in request and retry.".to_string())
})?;
let proof_str = req.proof.unwrap_or_else(|| "Verified".to_string());
TasksTool::Verify { id, proof } => {
let proof_str = proof.unwrap_or_else(|| "Verified".to_string());
let mut success = false;
let mut already_met = false;
let mut updated_task: Option<Task> = None;
@@ -436,41 +415,29 @@ impl McpTool for MilestonesHandler {
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
let req: MilestonesTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
match req.action {
MilestoneAction::Add => {
let title = req.title.ok_or_else(|| {
crate::error::AppError::Internal("Missing required parameter 'title' for action 'add'. Next step: Provide non-empty 'title' string in request and retry.".to_string())
})?;
let ns = req
.namespace
.unwrap_or_else(crate::models::default_namespace);
match req {
MilestonesTool::Add { title, namespace, target_date, description, deliverables, task_ids, repo_name } => {
let ns = namespace.unwrap_or_else(crate::models::default_namespace);
state.project.milestones.modify(|ms| {
ms.push(crate::models::Milestone {
id: uuid::Uuid::new_v4().to_string(),
title,
status: "pending".to_string(),
namespace: ns,
target_date: req
.target_date
target_date: target_date
.as_deref()
.and_then(|s| s.parse::<u64>().ok()),
description: req.description,
task_ids: req.task_ids.unwrap_or_default(),
deliverables: req.deliverables.unwrap_or_default(),
repo_name: req.repo_name,
description: description,
task_ids: task_ids.unwrap_or_default(),
deliverables: deliverables.unwrap_or_default(),
repo_name,
..Default::default()
})
});
crate::handlers::reconciliation::recalculate_milestones(&state);
Ok("Milestone added".to_string())
}
MilestoneAction::Update => {
let id = req.id.ok_or_else(|| {
crate::error::AppError::Internal("Missing required parameter 'id' for action 'update'. Next step: Provide milestone 'id' string in request and retry.".to_string())
})?;
let status = req.status.ok_or_else(|| {
crate::error::AppError::Internal("Missing required parameter 'status' for action 'update'. Next step: Provide milestone 'status' in request and retry.".to_string())
})?;
MilestonesTool::Update { id, status, deliverables, task_ids } => {
let mut found = false;
state.project.milestones.modify(|ms| {
for m in ms.iter_mut() {
@@ -482,9 +449,9 @@ impl McpTool for MilestonesHandler {
{
m.completed_at = Some(crate::handlers::utils::now_secs());
}
if let Some(task_ids) = &req.task_ids {
if let Some(task_ids) = &task_ids {
m.task_ids = task_ids.clone();
if let Some(deliverables) = &req.deliverables {
if let Some(deliverables) = &deliverables {
m.deliverables = deliverables.clone();
}
}
@@ -502,12 +469,12 @@ impl McpTool for MilestonesHandler {
))
}
}
MilestoneAction::List => {
MilestonesTool::List { namespace } => {
let data = state.project.milestones.read_with(|items| {
let filtered: Vec<_> = items
.iter()
.filter(|i| {
if let Some(ns) = &req.namespace {
if let Some(ns) = &namespace {
&i.namespace == ns
} else {
true