spec_kit_mcp/tools/
init.rs

1//! Spec-Kit Init Tool
2//!
3//! Initializes a new spec-kit project.
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_init tool
16#[derive(Debug, Deserialize, Serialize)]
17pub struct InitParams {
18    /// Project name
19    project_name: String,
20
21    /// Project path (defaults to current directory)
22    #[serde(default = "default_project_path")]
23    project_path: PathBuf,
24}
25
26fn default_project_path() -> PathBuf {
27    PathBuf::from(".")
28}
29
30/// Tool for initializing spec-kit projects
31pub struct InitTool {
32    cli: SpecKitCli,
33}
34
35impl InitTool {
36    /// Create a new init tool
37    pub fn new(cli: SpecKitCli) -> Self {
38        Self { cli }
39    }
40}
41
42#[async_trait]
43impl Tool for InitTool {
44    fn definition(&self) -> ToolDefinition {
45        ToolDefinition {
46            name: "speckit_init".to_string(),
47            description: "Initialize a new spec-kit project with proper directory structure and configuration files".to_string(),
48            input_schema: json!({
49                "type": "object",
50                "properties": {
51                    "project_name": {
52                        "type": "string",
53                        "description": "Name of the project to initialize"
54                    },
55                    "project_path": {
56                        "type": "string",
57                        "description": "Path where the project should be created",
58                        "default": "."
59                    }
60                },
61                "required": ["project_name"]
62            })
63        }
64    }
65
66    async fn execute(&self, params: Value) -> Result<ToolResult> {
67        let params: InitParams =
68            serde_json::from_value(params).context("Failed to parse init parameters")?;
69
70        tracing::info!(
71            project_name = %params.project_name,
72            project_path = %params.project_path.display(),
73            "Initializing spec-kit project"
74        );
75
76        // Execute spec-kit init command
77        let result = self
78            .cli
79            .init(&params.project_name, &params.project_path)
80            .await?;
81
82        if !result.is_success() {
83            return Ok(ToolResult {
84                content: vec![ContentBlock::text(format!(
85                    "Failed to initialize project: {}",
86                    result.stderr
87                ))],
88                is_error: Some(true),
89            });
90        }
91
92        let message = format!(
93            "Successfully initialized spec-kit project '{}' at {}\n\n\
94            Next steps:\n\
95            1. Navigate to the project: cd {}\n\
96            2. Create constitution: Use speckit_constitution tool\n\
97            3. Define requirements: Use speckit_specify tool\n\
98            4. Create technical plan: Use speckit_plan tool\n\
99            5. Generate tasks: Use speckit_tasks tool\n\
100            6. Implement: Use speckit_implement tool",
101            params.project_name,
102            params.project_path.display(),
103            params.project_path.display()
104        );
105
106        Ok(ToolResult {
107            content: vec![ContentBlock::text(message)],
108            is_error: None,
109        })
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116    use tempfile::tempdir;
117
118    #[tokio::test]
119    async fn test_init_tool_definition() {
120        let cli = SpecKitCli::new();
121        let tool = InitTool::new(cli);
122        let def = tool.definition();
123
124        assert_eq!(def.name, "speckit_init");
125        assert!(!def.description.is_empty());
126    }
127
128    #[tokio::test]
129    async fn test_init_tool_execute() {
130        let cli = SpecKitCli::new_test_mode();
131        let tool = InitTool::new(cli);
132
133        let dir = tempdir().unwrap();
134        let params = json!({
135            "project_name": "test-project",
136            "project_path": dir.path().to_str().unwrap()
137        });
138
139        let result = tool.execute(params).await.unwrap();
140        assert!(result.is_error.is_none() || !result.is_error.unwrap());
141    }
142}