clawft_core/
complexity.rs1pub struct TaskComplexityAnalyzer;
23
24impl TaskComplexityAnalyzer {
25 pub fn new() -> Self {
27 Self
28 }
29
30 pub fn analyze(&self, task: &str) -> f32 {
37 let lower = task.to_lowercase();
38 let words: Vec<&str> = lower.split_whitespace().collect();
39 let word_count = words.len();
40
41 let length_score = (word_count as f32 / 200.0).min(1.0) * 0.25;
43
44 let sentence_count = lower
47 .chars()
48 .filter(|c| *c == '.' || *c == '?' || *c == '!' || *c == ';')
49 .count()
50 .max(1); let sentence_score = ((sentence_count as f32 - 1.0) / 5.0).clamp(0.0, 1.0) * 0.15;
52
53 let technical_keywords = [
55 "api",
56 "database",
57 "schema",
58 "authentication",
59 "authorization",
60 "deploy",
61 "kubernetes",
62 "docker",
63 "algorithm",
64 "concurrency",
65 "async",
66 "microservice",
67 "architecture",
68 "security",
69 "encryption",
70 "sql",
71 "graphql",
72 "websocket",
73 "distributed",
74 "cache",
75 "index",
76 "migration",
77 ];
78 let tech_hits = technical_keywords
79 .iter()
80 .filter(|kw| lower.contains(**kw))
81 .count();
82 let tech_score = (tech_hits as f32 / 4.0).min(1.0) * 0.25;
83
84 let step_indicators = [
86 "first",
87 "second",
88 "third",
89 "then",
90 "finally",
91 "next",
92 "step 1",
93 "step 2",
94 "step 3",
95 "after that",
96 "followed by",
97 "in addition",
98 ];
99 let step_hits = step_indicators
100 .iter()
101 .filter(|si| lower.contains(**si))
102 .count();
103 let step_score = (step_hits as f32 / 3.0).min(1.0) * 0.20;
104
105 let code_score = if lower.contains("```") { 0.15 } else { 0.0 };
107
108 (length_score + sentence_score + tech_score + step_score + code_score).clamp(0.0, 1.0)
109 }
110}
111
112impl Default for TaskComplexityAnalyzer {
113 fn default() -> Self {
114 Self::new()
115 }
116}
117
118#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn simple_question_low_complexity() {
126 let a = TaskComplexityAnalyzer::new();
127 let score = a.analyze("What is 2 + 2?");
128 assert!(
129 score < 0.3,
130 "simple question should be low complexity, got {score}"
131 );
132 }
133
134 #[test]
135 fn complex_multipart_request_higher() {
136 let a = TaskComplexityAnalyzer::new();
137 let simple = a.analyze("What is Rust?");
138 let complex = a.analyze(
139 "First, design the database schema for a distributed authentication \
140 microservice. Then implement the API endpoints with proper authorization. \
141 Next, add caching with Redis. Finally, deploy to Kubernetes with \
142 encrypted secrets.",
143 );
144 assert!(
145 complex > simple,
146 "complex ({complex}) should beat simple ({simple})"
147 );
148 }
149
150 #[test]
151 fn technical_keywords_raise_complexity() {
152 let a = TaskComplexityAnalyzer::new();
153 let no_tech = a.analyze("Tell me a story about a cat.");
154 let with_tech = a.analyze("Set up the database schema and deploy to kubernetes.");
155 assert!(
156 with_tech > no_tech,
157 "technical ({with_tech}) should beat non-technical ({no_tech})"
158 );
159 }
160
161 #[test]
162 fn empty_task_returns_zero() {
163 let a = TaskComplexityAnalyzer::new();
164 let score = a.analyze("");
165 assert!(
166 score.abs() < f32::EPSILON,
167 "empty task should be 0.0, got {score}"
168 );
169 }
170
171 #[test]
172 fn score_clamped_to_unit_range() {
173 let a = TaskComplexityAnalyzer::new();
174 let score = a.analyze(
176 "First, design the api architecture for a distributed database microservice \
177 with authentication and authorization. Then implement concurrency with async \
178 websocket connections. Next, add encryption and caching. Finally, deploy to \
179 kubernetes with docker. Step 1: schema migration. Step 2: graphql index. \
180 Step 3: security audit. ```code here``` After that, followed by more work. \
181 In addition, add algorithm for sql cache.",
182 );
183 assert!(score <= 1.0, "score should be <= 1.0, got {score}");
184 assert!(score >= 0.0, "score should be >= 0.0, got {score}");
185 }
186
187 #[test]
188 fn code_fences_raise_complexity() {
189 let a = TaskComplexityAnalyzer::new();
190 let no_code = a.analyze("Explain this function.");
191 let with_code = a.analyze("Explain this function: ```fn main() {}```");
192 assert!(
193 with_code > no_code,
194 "with code ({with_code}) should beat without ({no_code})"
195 );
196 }
197
198 #[test]
199 fn step_indicators_raise_complexity() {
200 let a = TaskComplexityAnalyzer::new();
201 let no_steps = a.analyze("Do the thing.");
202 let with_steps = a.analyze("First do A, then do B, finally do C.");
203 assert!(
204 with_steps > no_steps,
205 "with steps ({with_steps}) should beat without ({no_steps})"
206 );
207 }
208
209 #[test]
210 fn default_trait_creates_valid_analyzer() {
211 let a = TaskComplexityAnalyzer::default();
212 let score = a.analyze("hello");
213 assert!(score >= 0.0);
214 assert!(score <= 1.0);
215 }
216
217 #[test]
218 fn longer_text_scores_higher_than_shorter() {
219 let a = TaskComplexityAnalyzer::new();
220 let short = a.analyze("help");
221 let long = a.analyze(&"word ".repeat(100));
222 assert!(
223 long > short,
224 "long text ({long}) should beat short ({short})"
225 );
226 }
227
228 #[test]
229 fn multiple_sentences_increase_score() {
230 let a = TaskComplexityAnalyzer::new();
231 let one = a.analyze("Do the task.");
232 let many = a.analyze("Do task A. Then task B. Also task C. Check task D. Review task E.");
233 assert!(
234 many > one,
235 "many sentences ({many}) should beat one ({one})"
236 );
237 }
238}