spec_kit_mcp/tools/
checklist.rs

1//! Spec-Kit Checklist Tool
2//!
3//! Generates validation checklists from specifications.
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_checklist tool
16#[derive(Debug, Deserialize, Serialize)]
17pub struct ChecklistParams {
18    /// Path to specification file
19    spec_file: PathBuf,
20
21    /// Include implementation checklist items
22    #[serde(default = "default_true")]
23    include_implementation: bool,
24
25    /// Include testing checklist items
26    #[serde(default = "default_true")]
27    include_testing: bool,
28
29    /// Output path for checklist
30    #[serde(default = "default_checklist_path")]
31    output_path: PathBuf,
32}
33
34fn default_true() -> bool {
35    true
36}
37
38fn default_checklist_path() -> PathBuf {
39    PathBuf::from("./speckit.checklist")
40}
41
42/// Tool for generating validation checklists
43pub struct ChecklistTool {
44    #[allow(dead_code)]
45    cli: SpecKitCli,
46}
47
48impl ChecklistTool {
49    /// Create a new checklist tool
50    pub fn new(cli: SpecKitCli) -> Self {
51        Self { cli }
52    }
53}
54
55#[async_trait]
56impl Tool for ChecklistTool {
57    fn definition(&self) -> ToolDefinition {
58        ToolDefinition {
59            name: "speckit_checklist".to_string(),
60            description: "Generate a validation checklist from the specification to ensure all requirements are met".to_string(),
61            input_schema: json!({
62                "type": "object",
63                "properties": {
64                    "spec_file": {
65                        "type": "string",
66                        "description": "Path to the specification file"
67                    },
68                    "include_implementation": {
69                        "type": "boolean",
70                        "default": true,
71                        "description": "Include implementation checklist items"
72                    },
73                    "include_testing": {
74                        "type": "boolean",
75                        "default": true,
76                        "description": "Include testing checklist items"
77                    },
78                    "output_path": {
79                        "type": "string",
80                        "description": "Path where checklist will be written",
81                        "default": "./speckit.checklist"
82                    }
83                },
84                "required": ["spec_file"]
85            })
86        }
87    }
88
89    async fn execute(&self, params: Value) -> Result<ToolResult> {
90        let params: ChecklistParams =
91            serde_json::from_value(params).context("Failed to parse checklist parameters")?;
92
93        tracing::info!(
94            spec_file = %params.spec_file.display(),
95            "Generating validation checklist"
96        );
97
98        // Read specification
99        let spec_content = tokio::fs::read_to_string(&params.spec_file)
100            .await
101            .context("Failed to read specification file")?;
102
103        // Generate checklist
104        let mut checklist = String::from("# Implementation & Validation Checklist\n\n");
105        checklist.push_str(&format!("Based on: {}\n\n", params.spec_file.display()));
106
107        // Requirements checklist
108        checklist.push_str("## Requirements Validation\n\n");
109
110        // Extract requirements (simple heuristic)
111        let mut req_count = 0;
112        for line in spec_content.lines() {
113            if line.trim().starts_with('-')
114                || line.trim().starts_with('*')
115                || line.contains("shall")
116                || line.contains("must")
117                || line.contains("should")
118            {
119                req_count += 1;
120                let requirement = line
121                    .trim()
122                    .trim_start_matches('-')
123                    .trim_start_matches('*')
124                    .trim();
125                if !requirement.is_empty() && requirement.len() < 100 {
126                    checklist.push_str(&format!("- [ ] {}\n", requirement));
127                }
128            }
129        }
130
131        if req_count == 0 {
132            checklist.push_str("- [ ] All specified requirements are implemented\n");
133            checklist.push_str("- [ ] Edge cases are handled\n");
134            checklist.push_str("- [ ] Error conditions are addressed\n");
135        }
136
137        // Implementation checklist
138        if params.include_implementation {
139            checklist.push_str("\n## Implementation Checklist\n\n");
140            checklist.push_str("- [ ] Code follows project style guide\n");
141            checklist.push_str("- [ ] Functions have clear documentation\n");
142            checklist.push_str("- [ ] Error handling is comprehensive\n");
143            checklist.push_str("- [ ] Input validation is performed\n");
144            checklist.push_str("- [ ] Logging is appropriate\n");
145            checklist.push_str("- [ ] Performance is acceptable\n");
146            checklist.push_str("- [ ] Security considerations addressed\n");
147        }
148
149        // Testing checklist
150        if params.include_testing {
151            checklist.push_str("\n## Testing Checklist\n\n");
152            checklist.push_str("- [ ] Unit tests written for all functions\n");
153            checklist.push_str("- [ ] Integration tests cover main workflows\n");
154            checklist.push_str("- [ ] Edge cases are tested\n");
155            checklist.push_str("- [ ] Error conditions are tested\n");
156            checklist.push_str("- [ ] Performance tests (if applicable)\n");
157            checklist.push_str("- [ ] All tests pass\n");
158            checklist.push_str("- [ ] Test coverage >80%\n");
159        }
160
161        // Quality checklist
162        checklist.push_str("\n## Quality Assurance\n\n");
163        checklist.push_str("- [ ] Code review completed\n");
164        checklist.push_str("- [ ] Documentation updated\n");
165        checklist.push_str("- [ ] CHANGELOG.md updated\n");
166        checklist.push_str("- [ ] No compiler warnings\n");
167        checklist.push_str("- [ ] Linter passes (clippy, etc.)\n");
168        checklist.push_str("- [ ] Dependencies are up to date\n");
169
170        // Deployment checklist
171        checklist.push_str("\n## Deployment Readiness\n\n");
172        checklist.push_str("- [ ] All tests pass in CI\n");
173        checklist.push_str("- [ ] Version number updated\n");
174        checklist.push_str("- [ ] Release notes prepared\n");
175        checklist.push_str("- [ ] Breaking changes documented\n");
176        checklist.push_str("- [ ] Migration guide provided (if needed)\n");
177
178        // Write checklist
179        tokio::fs::write(&params.output_path, &checklist)
180            .await
181            .context("Failed to write checklist")?;
182
183        let total_items = checklist.matches("- [ ]").count();
184
185        let message = format!(
186            "Validation checklist generated!\n\n\
187            Source: {}\n\
188            Total items: {}\n\
189            Output: {}\n\n\
190            Use this checklist to ensure all requirements are met and\n\
191            quality standards are maintained throughout implementation.",
192            params.spec_file.display(),
193            total_items,
194            params.output_path.display()
195        );
196
197        Ok(ToolResult {
198            content: vec![ContentBlock::text(message)],
199            is_error: None,
200        })
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207    use tempfile::tempdir;
208    use tokio::fs;
209
210    #[tokio::test]
211    async fn test_checklist_tool_definition() {
212        let cli = SpecKitCli::new();
213        let tool = ChecklistTool::new(cli);
214        let def = tool.definition();
215
216        assert_eq!(def.name, "speckit_checklist");
217        assert!(!def.description.is_empty());
218    }
219
220    #[tokio::test]
221    async fn test_checklist_tool_execute() {
222        let cli = SpecKitCli::new_test_mode();
223        let tool = ChecklistTool::new(cli);
224
225        let dir = tempdir().unwrap();
226        let spec_file = dir.path().join("spec.md");
227        let output_path = dir.path().join("checklist.md");
228
229        // Create spec with requirements
230        fs::write(
231            &spec_file,
232            "- User must login\n- System shall validate input\n- Should handle errors",
233        )
234        .await
235        .unwrap();
236
237        let params = json!({
238            "spec_file": spec_file.to_str().unwrap(),
239            "include_implementation": true,
240            "include_testing": true,
241            "output_path": output_path.to_str().unwrap()
242        });
243
244        let result = tool.execute(params).await.unwrap();
245        assert!(result.is_error.is_none() || !result.is_error.unwrap());
246        assert!(output_path.exists());
247
248        // Verify checklist has items
249        let content = fs::read_to_string(output_path).await.unwrap();
250        assert!(content.contains("- [ ]"));
251    }
252}