spec_kit_mcp/tools/
checklist.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 ChecklistParams {
18 spec_file: PathBuf,
20
21 #[serde(default = "default_true")]
23 include_implementation: bool,
24
25 #[serde(default = "default_true")]
27 include_testing: bool,
28
29 #[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
42pub struct ChecklistTool {
44 #[allow(dead_code)]
45 cli: SpecKitCli,
46}
47
48impl ChecklistTool {
49 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 let spec_content = tokio::fs::read_to_string(¶ms.spec_file)
100 .await
101 .context("Failed to read specification file")?;
102
103 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 checklist.push_str("## Requirements Validation\n\n");
109
110 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 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 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 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 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 tokio::fs::write(¶ms.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 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 let content = fs::read_to_string(output_path).await.unwrap();
250 assert!(content.contains("- [ ]"));
251 }
252}