Skip to main content

task_graph_mcp/tools/
gates.rs

1//! Gate checking tools.
2//!
3//! Tools for pre-flight checking gate requirements before status/phase transitions.
4
5use super::{get_string, make_tool_with_prompts};
6use crate::config::Prompts;
7use crate::config::workflows::WorkflowsConfig;
8use crate::db::Database;
9use crate::error::ToolError;
10use crate::gates::{GateResult, evaluate_gates};
11use anyhow::Result;
12use rmcp::model::Tool;
13use serde_json::{Value, json};
14
15/// Get the gate-related tools.
16pub fn get_tools(prompts: &Prompts) -> Vec<Tool> {
17    vec![make_tool_with_prompts(
18        "check_gates",
19        "Check gate requirements for a task before attempting a status/phase transition. Returns unsatisfied gates with overall status (pass/warn/fail).",
20        json!({
21            "task": {
22                "type": "string",
23                "description": "Task ID to check gates for"
24            }
25        }),
26        vec!["task"],
27        prompts,
28    )]
29}
30
31/// Check gates for a task.
32///
33/// This tool allows agents to pre-flight check gate requirements before attempting
34/// a status/phase transition. It evaluates all exit gates for the task's current
35/// status and phase, returning only unsatisfied gates.
36///
37/// # Response format
38/// ```json
39/// {
40///   "status": "pass" | "warn" | "fail",
41///   "gates": [
42///     {
43///       "type": "gate/tests",
44///       "enforcement": "reject",
45///       "description": "Attach test results",
46///       "satisfied": false
47///     }
48///   ]
49/// }
50/// ```
51///
52/// - "pass" = all gates satisfied OR only allow gates missing
53/// - "warn" = some warn gates missing (would need force=true)
54/// - "fail" = some reject gates missing (cannot proceed)
55pub fn check_gates(db: &Database, workflows: &WorkflowsConfig, args: Value) -> Result<Value> {
56    let task_id = get_string(&args, "task").ok_or_else(|| ToolError::missing_field("task"))?;
57
58    // Get the task to find current status and phase
59    let task = db
60        .get_task(&task_id)?
61        .ok_or_else(|| ToolError::new(crate::error::ErrorCode::TaskNotFound, "Task not found"))?;
62
63    // Collect all applicable gates (status + phase exit gates)
64    let mut all_gates = Vec::new();
65
66    // Get exit gates for current status
67    let status_gates = workflows.get_status_exit_gates(&task.status);
68    all_gates.extend(status_gates.into_iter().cloned());
69
70    // Get exit gates for current phase (if set)
71    if let Some(ref phase) = task.phase {
72        let phase_gates = workflows.get_phase_exit_gates(phase);
73        all_gates.extend(phase_gates.into_iter().cloned());
74    }
75
76    // Evaluate gates
77    let result = evaluate_gates(db, &task_id, &all_gates)?;
78
79    // Build response in the required format
80    let gates: Vec<Value> = result
81        .unsatisfied_gates
82        .iter()
83        .map(gate_result_to_json)
84        .collect();
85
86    Ok(json!({
87        "status": result.status,
88        "gates": gates
89    }))
90}
91
92/// Convert a GateResult to the response JSON format.
93fn gate_result_to_json(gate: &GateResult) -> Value {
94    json!({
95        "type": gate.gate_type,
96        "enforcement": gate.enforcement,
97        "description": gate.description,
98        "satisfied": gate.satisfied
99    })
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn test_gate_result_to_json() {
108        use crate::config::GateEnforcement;
109
110        let gate = GateResult {
111            gate_type: "gate/tests".to_string(),
112            enforcement: GateEnforcement::Reject,
113            description: "Attach test results".to_string(),
114            satisfied: false,
115        };
116
117        let json = gate_result_to_json(&gate);
118        assert_eq!(json["type"], "gate/tests");
119        assert_eq!(json["enforcement"], "reject");
120        assert_eq!(json["description"], "Attach test results");
121        assert_eq!(json["satisfied"], false);
122    }
123}