1include!(concat!(env!("OUT_DIR"), "/rules_data.rs"));
40
41pub fn rule_count() -> usize {
43 RULES_DATA.len()
44}
45
46pub fn get_rule_name(id: &str) -> Option<&'static str> {
48 RULES_DATA
49 .iter()
50 .find(|(rule_id, _)| *rule_id == id)
51 .map(|(_, name)| *name)
52}
53
54pub fn valid_tools() -> &'static [&'static str] {
58 VALID_TOOLS
59}
60
61pub fn get_tool_for_prefix(prefix: &str) -> Option<&'static str> {
77 TOOL_RULE_PREFIXES
78 .iter()
79 .find(|(p, _)| *p == prefix)
80 .map(|(_, tool)| *tool)
81}
82
83pub fn get_prefixes_for_tool(tool: &str) -> Vec<&'static str> {
94 TOOL_RULE_PREFIXES
95 .iter()
96 .filter(|(_, t)| t.eq_ignore_ascii_case(tool))
97 .map(|(prefix, _)| *prefix)
98 .collect()
99}
100
101pub fn is_valid_tool(tool: &str) -> bool {
105 VALID_TOOLS.iter().any(|t| t.eq_ignore_ascii_case(tool))
106}
107
108pub fn normalize_tool_name(tool: &str) -> Option<&'static str> {
122 VALID_TOOLS
123 .iter()
124 .find(|t| t.eq_ignore_ascii_case(tool))
125 .copied()
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn test_rules_data_not_empty() {
134 assert!(!RULES_DATA.is_empty(), "RULES_DATA should not be empty");
135 }
136
137 #[test]
138 fn test_rule_count() {
139 assert_eq!(rule_count(), RULES_DATA.len());
140 }
141
142 #[test]
143 fn test_get_rule_name_exists() {
144 let name = get_rule_name("AS-001");
146 assert!(name.is_some(), "AS-001 should exist");
147 }
148
149 #[test]
150 fn test_get_rule_name_not_exists() {
151 let name = get_rule_name("NONEXISTENT-999");
152 assert!(name.is_none(), "Nonexistent rule should return None");
153 }
154
155 #[test]
156 fn test_no_duplicate_ids() {
157 let mut ids: Vec<&str> = RULES_DATA.iter().map(|(id, _)| *id).collect();
158 let original_len = ids.len();
159 ids.sort();
160 ids.dedup();
161 assert_eq!(ids.len(), original_len, "Should have no duplicate rule IDs");
162 }
163
164 #[test]
167 fn test_valid_tools_not_empty() {
168 assert!(!VALID_TOOLS.is_empty(), "VALID_TOOLS should not be empty");
169 }
170
171 #[test]
172 fn test_valid_tools_contains_claude_code() {
173 assert!(
174 VALID_TOOLS.contains(&"claude-code"),
175 "VALID_TOOLS should contain 'claude-code'"
176 );
177 }
178
179 #[test]
180 fn test_valid_tools_contains_github_copilot() {
181 assert!(
182 VALID_TOOLS.contains(&"github-copilot"),
183 "VALID_TOOLS should contain 'github-copilot'"
184 );
185 }
186
187 #[test]
188 fn test_valid_tools_contains_cursor() {
189 assert!(
190 VALID_TOOLS.contains(&"cursor"),
191 "VALID_TOOLS should contain 'cursor'"
192 );
193 }
194
195 #[test]
196 fn test_valid_tools_helper() {
197 let tools = valid_tools();
198 assert!(!tools.is_empty());
199 assert!(tools.contains(&"claude-code"));
200 }
201
202 #[test]
205 fn test_tool_rule_prefixes_not_empty() {
206 assert!(
207 !TOOL_RULE_PREFIXES.is_empty(),
208 "TOOL_RULE_PREFIXES should not be empty"
209 );
210 }
211
212 #[test]
213 fn test_tool_rule_prefixes_cc_hk() {
214 let found = TOOL_RULE_PREFIXES
216 .iter()
217 .find(|(prefix, _)| *prefix == "CC-HK-");
218 assert!(found.is_some(), "Should have CC-HK- prefix");
219 assert_eq!(found.unwrap().1, "claude-code");
220 }
221
222 #[test]
223 fn test_tool_rule_prefixes_cop() {
224 let found = TOOL_RULE_PREFIXES
226 .iter()
227 .find(|(prefix, _)| *prefix == "COP-");
228 assert!(found.is_some(), "Should have COP- prefix");
229 assert_eq!(found.unwrap().1, "github-copilot");
230 }
231
232 #[test]
233 fn test_tool_rule_prefixes_cur() {
234 let found = TOOL_RULE_PREFIXES
236 .iter()
237 .find(|(prefix, _)| *prefix == "CUR-");
238 assert!(found.is_some(), "Should have CUR- prefix");
239 assert_eq!(found.unwrap().1, "cursor");
240 }
241
242 #[test]
243 fn test_get_tool_for_prefix_claude_code() {
244 assert_eq!(get_tool_for_prefix("CC-HK-"), Some("claude-code"));
245 assert_eq!(get_tool_for_prefix("CC-SK-"), Some("claude-code"));
246 assert_eq!(get_tool_for_prefix("CC-AG-"), Some("claude-code"));
247 assert_eq!(get_tool_for_prefix("CC-PL-"), Some("claude-code"));
248 assert_eq!(get_tool_for_prefix("CC-MEM-"), None);
251 }
252
253 #[test]
254 fn test_get_tool_for_prefix_copilot() {
255 assert_eq!(get_tool_for_prefix("COP-"), Some("github-copilot"));
256 }
257
258 #[test]
259 fn test_get_tool_for_prefix_cursor() {
260 assert_eq!(get_tool_for_prefix("CUR-"), Some("cursor"));
261 }
262
263 #[test]
264 fn test_get_tool_for_prefix_generic() {
265 assert_eq!(get_tool_for_prefix("MCP-"), None);
268 assert_eq!(get_tool_for_prefix("XML-"), None);
269 assert_eq!(get_tool_for_prefix("XP-"), None);
270 }
274
275 #[test]
276 fn test_get_tool_for_prefix_unknown() {
277 assert_eq!(get_tool_for_prefix("UNKNOWN-"), None);
278 }
279
280 #[test]
283 fn test_mixed_tool_prefix_as() {
284 assert_eq!(get_tool_for_prefix("AS-"), None);
287 }
288
289 #[test]
290 fn test_mixed_tool_prefix_cc_mem() {
291 assert_eq!(get_tool_for_prefix("CC-MEM-"), None);
295 }
296
297 #[test]
298 fn test_consistent_tool_prefix_cc_hk() {
299 assert_eq!(get_tool_for_prefix("CC-HK-"), Some("claude-code"));
302 }
303
304 #[test]
305 fn test_get_prefixes_for_tool_claude_code() {
306 let prefixes = get_prefixes_for_tool("claude-code");
307 assert!(!prefixes.is_empty());
308 assert!(prefixes.contains(&"CC-HK-"));
309 assert!(prefixes.contains(&"CC-SK-"));
310 assert!(prefixes.contains(&"CC-AG-"));
311 assert!(prefixes.contains(&"CC-PL-"));
312 assert!(!prefixes.contains(&"CC-MEM-"));
315 }
316
317 #[test]
318 fn test_get_prefixes_for_tool_copilot() {
319 let prefixes = get_prefixes_for_tool("github-copilot");
320 assert!(!prefixes.is_empty());
321 assert!(prefixes.contains(&"COP-"));
322 }
323
324 #[test]
325 fn test_get_prefixes_for_tool_cursor() {
326 let prefixes = get_prefixes_for_tool("cursor");
327 assert!(!prefixes.is_empty());
328 assert!(prefixes.contains(&"CUR-"));
329 }
330
331 #[test]
332 fn test_get_prefixes_for_tool_unknown() {
333 let prefixes = get_prefixes_for_tool("unknown-tool");
334 assert!(prefixes.is_empty());
335 }
336
337 #[test]
340 fn test_is_valid_tool_claude_code() {
341 assert!(is_valid_tool("claude-code"));
342 assert!(is_valid_tool("Claude-Code")); assert!(is_valid_tool("CLAUDE-CODE")); }
345
346 #[test]
347 fn test_is_valid_tool_copilot() {
348 assert!(is_valid_tool("github-copilot"));
349 assert!(is_valid_tool("GitHub-Copilot")); }
351
352 #[test]
353 fn test_is_valid_tool_unknown() {
354 assert!(!is_valid_tool("unknown-tool"));
355 assert!(!is_valid_tool(""));
356 }
357
358 #[test]
361 fn test_normalize_tool_name_claude_code() {
362 assert_eq!(normalize_tool_name("claude-code"), Some("claude-code"));
363 assert_eq!(normalize_tool_name("Claude-Code"), Some("claude-code"));
364 assert_eq!(normalize_tool_name("CLAUDE-CODE"), Some("claude-code"));
365 }
366
367 #[test]
368 fn test_normalize_tool_name_copilot() {
369 assert_eq!(
370 normalize_tool_name("github-copilot"),
371 Some("github-copilot")
372 );
373 assert_eq!(
374 normalize_tool_name("GitHub-Copilot"),
375 Some("github-copilot")
376 );
377 }
378
379 #[test]
380 fn test_normalize_tool_name_unknown() {
381 assert_eq!(normalize_tool_name("unknown-tool"), None);
382 assert_eq!(normalize_tool_name(""), None);
383 }
384
385 #[test]
388 fn test_get_prefixes_for_tool_empty_string() {
389 let prefixes = get_prefixes_for_tool("");
391 assert!(
392 prefixes.is_empty(),
393 "Empty string tool should return empty Vec"
394 );
395 }
396
397 #[test]
398 fn test_get_prefixes_for_tool_unknown_tool() {
399 let prefixes = get_prefixes_for_tool("nonexistent-tool");
401 assert!(prefixes.is_empty(), "Unknown tool should return empty Vec");
402 }
403
404 #[test]
405 fn test_get_prefixes_for_tool_claude_code_multiple_prefixes() {
406 let prefixes = get_prefixes_for_tool("claude-code");
408 assert!(
409 prefixes.len() > 1,
410 "claude-code should have multiple prefixes, got {}",
411 prefixes.len()
412 );
413 assert!(
415 prefixes.contains(&"CC-HK-"),
416 "claude-code prefixes should include CC-HK-"
417 );
418 assert!(
419 prefixes.contains(&"CC-SK-"),
420 "claude-code prefixes should include CC-SK-"
421 );
422 }
423
424 #[test]
427 fn test_get_tool_for_prefix_empty_string() {
428 assert_eq!(
430 get_tool_for_prefix(""),
431 None,
432 "Empty prefix should return None"
433 );
434 }
435
436 #[test]
437 fn test_get_tool_for_prefix_unknown_prefix() {
438 assert_eq!(
440 get_tool_for_prefix("NONEXISTENT-"),
441 None,
442 "Unknown prefix should return None"
443 );
444 assert_eq!(
445 get_tool_for_prefix("XX-"),
446 None,
447 "XX- prefix should return None"
448 );
449 }
450
451 #[test]
452 fn test_get_tool_for_prefix_partial_match_not_supported() {
453 assert_eq!(
455 get_tool_for_prefix("CC-"),
456 None,
457 "Partial prefix CC- (without HK/SK/AG) should not match"
458 );
459 assert_eq!(
460 get_tool_for_prefix("C"),
461 None,
462 "Single character should not match"
463 );
464 }
465}