1use super::types::*;
4use std::collections::HashMap;
5use std::sync::Arc;
6
7#[derive(Debug, Clone)]
9pub enum ClarificationStrategy {
10 Automatic,
12 Interactive,
14 Template(String),
16}
17
18#[derive(Debug, Clone)]
20pub struct ClarificationQuestion {
21 pub id: String,
23 pub question: String,
25 pub question_type: QuestionType,
27 pub options: Option<Vec<String>>,
29 pub default_answer: Option<String>,
31 pub required: bool,
33}
34
35#[derive(Debug, Clone)]
37pub enum QuestionType {
38 OpenEnded,
40 SingleChoice,
42 MultipleChoice,
44 Confirmation,
46 NumericRange { min: i64, max: i64 },
48}
49
50pub struct ClarificationSession {
52 pub session_id: String,
54 pub todo_id: String,
56 pub raw_idea: String,
58 pub answered_questions: Vec<(ClarificationQuestion, String)>,
60 pub pending_questions: Vec<ClarificationQuestion>,
62 pub clarified_requirement: Option<ProjectRequirement>,
64}
65
66pub struct RequirementClarifier {
68 strategy: ClarificationStrategy,
70 prompt_templates: HashMap<String, String>,
72 clarifier_fn: Option<Arc<dyn Fn(&str) -> Vec<ClarificationQuestion> + Send + Sync>>,
74}
75
76impl RequirementClarifier {
77 pub fn new(strategy: ClarificationStrategy) -> Self {
79 let mut prompt_templates = HashMap::new();
80
81 prompt_templates.insert(
82 "analyze_requirement".to_string(),
83 r#"分析以下用户需求,提取关键信息:
84
85用户需求:{raw_idea}
86
87请回答以下问题:
881. 核心目标是什么?
892. 有哪些具体的功能要求?
903. 有哪些约束条件或限制?
914. 成功的验收标准是什么?
925. 是否有依赖项或前置条件?
93
94请以JSON格式返回分析结果。"#
95 .to_string(),
96 );
97
98 Self {
99 strategy,
100 prompt_templates,
101 clarifier_fn: None,
102 }
103 }
104
105 pub fn with_custom_clarifier<F>(mut self, clarifier: F) -> Self
107 where
108 F: Fn(&str) -> Vec<ClarificationQuestion> + Send + Sync + 'static,
109 {
110 self.clarifier_fn = Some(Arc::new(clarifier));
111 self
112 }
113
114 pub fn add_prompt_template(&mut self, name: &str, template: &str) {
116 self.prompt_templates
117 .insert(name.to_string(), template.to_string());
118 }
119
120 pub async fn start_session(&self, todo_id: &str, raw_idea: &str) -> ClarificationSession {
122 let session_id = format!(
123 "clarify_{}",
124 std::time::SystemTime::now()
125 .duration_since(std::time::UNIX_EPOCH)
126 .unwrap_or_default()
127 .as_millis()
128 );
129
130 let pending_questions = self.generate_questions(raw_idea).await;
131
132 ClarificationSession {
133 session_id,
134 todo_id: todo_id.to_string(),
135 raw_idea: raw_idea.to_string(),
136 answered_questions: Vec::new(),
137 pending_questions,
138 clarified_requirement: None,
139 }
140 }
141
142 async fn generate_questions(&self, raw_idea: &str) -> Vec<ClarificationQuestion> {
144 if let Some(ref clarifier) = self.clarifier_fn {
145 return clarifier(raw_idea);
146 }
147
148 match &self.strategy {
149 ClarificationStrategy::Automatic => self.generate_automatic_questions(raw_idea),
150 ClarificationStrategy::Interactive => self.generate_interactive_questions(raw_idea),
151 ClarificationStrategy::Template(template_name) => {
152 self.generate_template_questions(raw_idea, template_name)
153 }
154 }
155 }
156
157 fn generate_automatic_questions(&self, _raw_idea: &str) -> Vec<ClarificationQuestion> {
158 vec![
159 ClarificationQuestion {
160 id: "scope".to_string(),
161 question: "请描述这个需求的具体范围和边界?".to_string(),
162 question_type: QuestionType::OpenEnded,
163 options: None,
164 default_answer: None,
165 required: true,
166 },
167 ClarificationQuestion {
168 id: "priority".to_string(),
169 question: "这个需求的紧急程度如何?".to_string(),
170 question_type: QuestionType::SingleChoice,
171 options: Some(vec![
172 "紧急(今天完成)".to_string(),
173 "高优先级(本周完成)".to_string(),
174 "中优先级(本月完成)".to_string(),
175 "低优先级(有空再做)".to_string(),
176 ]),
177 default_answer: Some("中优先级(本月完成)".to_string()),
178 required: true,
179 },
180 ClarificationQuestion {
181 id: "acceptance".to_string(),
182 question: "如何判断这个需求已经完成?有什么验收标准?".to_string(),
183 question_type: QuestionType::OpenEnded,
184 options: None,
185 default_answer: None,
186 required: true,
187 },
188 ClarificationQuestion {
189 id: "dependencies".to_string(),
190 question: "完成这个需求是否需要其他前置条件或依赖?".to_string(),
191 question_type: QuestionType::OpenEnded,
192 options: None,
193 default_answer: Some("无特殊依赖".to_string()),
194 required: false,
195 },
196 ]
197 }
198
199 fn generate_interactive_questions(&self, _raw_idea: &str) -> Vec<ClarificationQuestion> {
200 vec![
201 ClarificationQuestion {
202 id: "confirm_understanding".to_string(),
203 question: "我理解您想要...,这个理解正确吗?".to_string(),
204 question_type: QuestionType::Confirmation,
205 options: None,
206 default_answer: None,
207 required: true,
208 },
209 ClarificationQuestion {
210 id: "additional_details".to_string(),
211 question: "是否有其他需要补充的细节?".to_string(),
212 question_type: QuestionType::OpenEnded,
213 options: None,
214 default_answer: None,
215 required: false,
216 },
217 ]
218 }
219
220 fn generate_template_questions(
221 &self,
222 _raw_idea: &str,
223 template_name: &str,
224 ) -> Vec<ClarificationQuestion> {
225 match template_name {
226 "software_feature" => vec![
227 ClarificationQuestion {
228 id: "user_story".to_string(),
229 question: "请用「作为...我希望...以便...」的格式描述需求".to_string(),
230 question_type: QuestionType::OpenEnded,
231 options: None,
232 default_answer: None,
233 required: true,
234 },
235 ClarificationQuestion {
236 id: "affected_modules".to_string(),
237 question: "这个功能会影响哪些模块或组件?".to_string(),
238 question_type: QuestionType::MultipleChoice,
239 options: Some(vec![
240 "前端UI".to_string(),
241 "后端API".to_string(),
242 "数据库".to_string(),
243 "第三方集成".to_string(),
244 ]),
245 default_answer: None,
246 required: true,
247 },
248 ],
249 _ => self.generate_automatic_questions(_raw_idea),
250 }
251 }
252
253 pub async fn answer_question(
255 &self,
256 session: &mut ClarificationSession,
257 question_id: &str,
258 answer: &str,
259 ) -> anyhow::Result<()> {
260 let idx = session
261 .pending_questions
262 .iter()
263 .position(|q| q.id == question_id);
264
265 if let Some(idx) = idx {
266 let question = session.pending_questions.remove(idx);
267 session
268 .answered_questions
269 .push((question, answer.to_string()));
270 Ok(())
271 } else {
272 Err(anyhow::anyhow!("Question not found: {}", question_id))
273 }
274 }
275
276 pub async fn finalize_requirement(
278 &self,
279 session: &mut ClarificationSession,
280 ) -> anyhow::Result<ProjectRequirement> {
281 let requirement = self.synthesize_requirement(session).await?;
282 session.clarified_requirement = Some(requirement.clone());
283 Ok(requirement)
284 }
285
286 async fn synthesize_requirement(
287 &self,
288 session: &ClarificationSession,
289 ) -> anyhow::Result<ProjectRequirement> {
290 let mut acceptance_criteria = Vec::new();
291 for (question, answer) in &session.answered_questions {
292 if question.id == "acceptance" {
293 for line in answer.lines() {
294 let line = line.trim();
295 if !line.is_empty() {
296 acceptance_criteria.push(line.to_string());
297 }
298 }
299 }
300 }
301
302 if acceptance_criteria.is_empty() {
303 acceptance_criteria.push("功能按预期工作".to_string());
304 acceptance_criteria.push("无明显错误".to_string());
305 }
306
307 let subtasks = self.decompose_into_subtasks(&session.raw_idea);
308
309 let mut dependencies = Vec::new();
310 for (question, answer) in &session.answered_questions {
311 if question.id == "dependencies" && answer != "无特殊依赖" {
312 dependencies.push(answer.clone());
313 }
314 }
315
316 Ok(ProjectRequirement {
317 title: self.generate_title(&session.raw_idea),
318 description: session.raw_idea.clone(),
319 acceptance_criteria,
320 subtasks,
321 dependencies,
322 estimated_effort: None,
323 resources: Vec::new(),
324 })
325 }
326
327 fn generate_title(&self, raw_idea: &str) -> String {
328 let title: String = raw_idea.chars().take(50).collect();
329 if raw_idea.len() > 50 {
330 format!("{}...", title)
331 } else {
332 title
333 }
334 }
335
336 fn decompose_into_subtasks(&self, raw_idea: &str) -> Vec<Subtask> {
337 let mut subtasks = Vec::new();
338 let idea_lower = raw_idea.to_lowercase();
339
340 if idea_lower.contains("api") || idea_lower.contains("接口") {
341 subtasks.push(Subtask {
342 id: "subtask_api_design".to_string(),
343 description: "设计API接口规范".to_string(),
344 required_capabilities: vec!["api_design".to_string()],
345 order: 1,
346 depends_on: Vec::new(),
347 });
348 subtasks.push(Subtask {
349 id: "subtask_api_impl".to_string(),
350 description: "实现API接口".to_string(),
351 required_capabilities: vec!["backend".to_string()],
352 order: 2,
353 depends_on: vec!["subtask_api_design".to_string()],
354 });
355 }
356
357 if idea_lower.contains("ui") || idea_lower.contains("界面") || idea_lower.contains("前端")
358 {
359 subtasks.push(Subtask {
360 id: "subtask_ui_design".to_string(),
361 description: "设计UI界面".to_string(),
362 required_capabilities: vec!["ui_design".to_string()],
363 order: 1,
364 depends_on: Vec::new(),
365 });
366 subtasks.push(Subtask {
367 id: "subtask_ui_impl".to_string(),
368 description: "实现UI界面".to_string(),
369 required_capabilities: vec!["frontend".to_string()],
370 order: 2,
371 depends_on: vec!["subtask_ui_design".to_string()],
372 });
373 }
374
375 if subtasks.is_empty() {
376 subtasks.push(Subtask {
377 id: "subtask_main".to_string(),
378 description: raw_idea.to_string(),
379 required_capabilities: vec!["general".to_string()],
380 order: 1,
381 depends_on: Vec::new(),
382 });
383 }
384
385 subtasks
386 }
387
388 pub async fn quick_clarify(
390 &self,
391 todo_id: &str,
392 raw_idea: &str,
393 ) -> anyhow::Result<ProjectRequirement> {
394 let mut session = self.start_session(todo_id, raw_idea).await;
395
396 let pending = session.pending_questions.clone();
397 for question in pending {
398 let answer = question
399 .default_answer
400 .clone()
401 .unwrap_or_else(|| "待定".to_string());
402 self.answer_question(&mut session, &question.id, &answer)
403 .await?;
404 }
405
406 self.finalize_requirement(&mut session).await
407 }
408}
409
410impl Default for RequirementClarifier {
411 fn default() -> Self {
412 Self::new(ClarificationStrategy::Automatic)
413 }
414}
415
416#[cfg(test)]
417mod tests {
418 use super::*;
419
420 #[tokio::test]
421 async fn test_start_session() {
422 let clarifier = RequirementClarifier::new(ClarificationStrategy::Automatic);
423 let session = clarifier.start_session("todo_1", "Build a REST API").await;
424
425 assert_eq!(session.todo_id, "todo_1");
426 assert!(!session.pending_questions.is_empty());
427 }
428
429 #[tokio::test]
430 async fn test_quick_clarify() {
431 let clarifier = RequirementClarifier::new(ClarificationStrategy::Automatic);
432 let requirement = clarifier
433 .quick_clarify("todo_1", "Build a REST API")
434 .await
435 .unwrap();
436
437 assert!(!requirement.title.is_empty());
438 assert!(!requirement.acceptance_criteria.is_empty());
439 }
440}