spec_kit_mcp/tools/
implement.rs

1//! Spec-Kit Implement Tool
2//!
3//! Executes implementation according to the task list.
4
5use anyhow::{Context, Result};
6use async_trait::async_trait;
7use serde::{Deserialize, Serialize};
8use serde_json::{json, Value};
9use std::path::PathBuf;
10
11use crate::mcp::types::{ContentBlock, ToolDefinition, ToolResult};
12use crate::speckit::SpecKitCli;
13use crate::tools::Tool;
14
15/// Parameters for the speckit_implement tool
16#[derive(Debug, Deserialize, Serialize)]
17pub struct ImplementParams {
18    /// Path to tasks file
19    task_file: PathBuf,
20
21    /// Additional context for implementation
22    #[serde(default)]
23    context: Option<String>,
24
25    /// Output directory for implementation
26    #[serde(default = "default_output_dir")]
27    output_dir: PathBuf,
28}
29
30fn default_output_dir() -> PathBuf {
31    PathBuf::from("./src")
32}
33
34/// Tool for executing implementation
35pub struct ImplementTool {
36    #[allow(dead_code)]
37    cli: SpecKitCli,
38}
39
40impl ImplementTool {
41    /// Create a new implement tool
42    pub fn new(cli: SpecKitCli) -> Self {
43        Self { cli }
44    }
45}
46
47#[async_trait]
48impl Tool for ImplementTool {
49    fn definition(&self) -> ToolDefinition {
50        ToolDefinition {
51            name: "speckit_implement".to_string(),
52            description: "Execute implementation according to the task list, generating code and documentation".to_string(),
53            input_schema: json!({
54                "type": "object",
55                "properties": {
56                    "task_file": {
57                        "type": "string",
58                        "description": "Path to the tasks file (speckit.tasks)"
59                    },
60                    "context": {
61                        "type": "string",
62                        "description": "Additional context for implementation (e.g., existing code patterns, constraints)"
63                    },
64                    "output_dir": {
65                        "type": "string",
66                        "description": "Directory where code will be generated",
67                        "default": "./src"
68                    }
69                },
70                "required": ["task_file"]
71            })
72        }
73    }
74
75    async fn execute(&self, params: Value) -> Result<ToolResult> {
76        let params: ImplementParams =
77            serde_json::from_value(params).context("Failed to parse implement parameters")?;
78
79        tracing::info!(
80            task_file = %params.task_file.display(),
81            output_dir = %params.output_dir.display(),
82            "Executing implementation"
83        );
84
85        // Read the tasks file
86        let tasks_content = tokio::fs::read_to_string(&params.task_file)
87            .await
88            .context("Failed to read tasks file")?;
89
90        // For now, we return guidance since actual implementation
91        // requires AI-generated code based on specs
92        let message = format!(
93            "Implementation guidance based on {}\n\n\
94            The task file contains the following items:\n\
95            {}\n\n\
96            Implementation approach:\n\
97            1. Review each task in the task list\n\
98            2. Implement tasks in order, respecting dependencies\n\
99            3. Write tests alongside implementation\n\
100            4. Update documentation as you go\n\
101            5. Commit after completing each logical unit\n\n\
102            Context: {}\n\n\
103            Output directory: {}\n\n\
104            Next step: Begin implementing the first task",
105            params.task_file.display(),
106            tasks_content
107                .lines()
108                .take(10)
109                .collect::<Vec<_>>()
110                .join("\n"),
111            params.context.as_deref().unwrap_or("None provided"),
112            params.output_dir.display()
113        );
114
115        Ok(ToolResult {
116            content: vec![ContentBlock::text(message)],
117            is_error: None,
118        })
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125    use tempfile::tempdir;
126    use tokio::fs;
127
128    #[tokio::test]
129    async fn test_implement_tool_definition() {
130        let cli = SpecKitCli::new();
131        let tool = ImplementTool::new(cli);
132        let def = tool.definition();
133
134        assert_eq!(def.name, "speckit_implement");
135        assert!(!def.description.is_empty());
136    }
137
138    #[tokio::test]
139    async fn test_implement_tool_execute() {
140        let cli = SpecKitCli::new_test_mode();
141        let tool = ImplementTool::new(cli);
142
143        let dir = tempdir().unwrap();
144        let task_file = dir.path().join("tasks.md");
145
146        // Create dummy task file
147        fs::write(&task_file, "Task 1: Implement feature\nTask 2: Write tests")
148            .await
149            .unwrap();
150
151        let params = json!({
152            "task_file": task_file.to_str().unwrap(),
153            "context": "Using Rust 2021 edition",
154            "output_dir": dir.path().join("src").to_str().unwrap()
155        });
156
157        let result = tool.execute(params).await.unwrap();
158        assert!(result.is_error.is_none() || !result.is_error.unwrap());
159    }
160}