spec_kit_mcp/tools/
implement.rs1use 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#[derive(Debug, Deserialize, Serialize)]
17pub struct ImplementParams {
18 task_file: PathBuf,
20
21 #[serde(default)]
23 context: Option<String>,
24
25 #[serde(default = "default_output_dir")]
27 output_dir: PathBuf,
28}
29
30fn default_output_dir() -> PathBuf {
31 PathBuf::from("./src")
32}
33
34pub struct ImplementTool {
36 #[allow(dead_code)]
37 cli: SpecKitCli,
38}
39
40impl ImplementTool {
41 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 let tasks_content = tokio::fs::read_to_string(¶ms.task_file)
87 .await
88 .context("Failed to read tasks file")?;
89
90 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 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}