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 #[allow(clippy::const_is_empty)]
134 fn test_rules_data_not_empty() {
135 assert!(!RULES_DATA.is_empty(), "RULES_DATA should not be empty");
136 }
137
138 #[test]
139 fn test_rule_count() {
140 assert_eq!(rule_count(), RULES_DATA.len());
141 }
142
143 #[test]
144 fn test_get_rule_name_exists() {
145 let name = get_rule_name("AS-001");
147 assert!(name.is_some(), "AS-001 should exist");
148 }
149
150 #[test]
151 fn test_get_rule_name_not_exists() {
152 let name = get_rule_name("NONEXISTENT-999");
153 assert!(name.is_none(), "Nonexistent rule should return None");
154 }
155
156 #[test]
157 fn test_no_duplicate_ids() {
158 let mut ids: Vec<&str> = RULES_DATA.iter().map(|(id, _)| *id).collect();
159 let original_len = ids.len();
160 ids.sort();
161 ids.dedup();
162 assert_eq!(ids.len(), original_len, "Should have no duplicate rule IDs");
163 }
164
165 #[test]
168 #[allow(clippy::const_is_empty)]
169 fn test_valid_tools_not_empty() {
170 assert!(!VALID_TOOLS.is_empty(), "VALID_TOOLS should not be empty");
171 }
172
173 #[test]
174 fn test_valid_tools_contains_claude_code() {
175 assert!(
176 VALID_TOOLS.contains(&"claude-code"),
177 "VALID_TOOLS should contain 'claude-code'"
178 );
179 }
180
181 #[test]
182 fn test_valid_tools_contains_github_copilot() {
183 assert!(
184 VALID_TOOLS.contains(&"github-copilot"),
185 "VALID_TOOLS should contain 'github-copilot'"
186 );
187 }
188
189 #[test]
190 fn test_valid_tools_contains_cursor() {
191 assert!(
192 VALID_TOOLS.contains(&"cursor"),
193 "VALID_TOOLS should contain 'cursor'"
194 );
195 }
196
197 #[test]
198 fn test_valid_tools_helper() {
199 let tools = valid_tools();
200 assert!(!tools.is_empty());
201 assert!(tools.contains(&"claude-code"));
202 }
203
204 #[test]
207 #[allow(clippy::const_is_empty)]
208 fn test_tool_rule_prefixes_not_empty() {
209 assert!(
210 !TOOL_RULE_PREFIXES.is_empty(),
211 "TOOL_RULE_PREFIXES should not be empty"
212 );
213 }
214
215 #[test]
216 fn test_tool_rule_prefixes_cc_hk() {
217 let found = TOOL_RULE_PREFIXES
219 .iter()
220 .find(|(prefix, _)| *prefix == "CC-HK-");
221 assert!(found.is_some(), "Should have CC-HK- prefix");
222 assert_eq!(found.unwrap().1, "claude-code");
223 }
224
225 #[test]
226 fn test_tool_rule_prefixes_cop() {
227 let found = TOOL_RULE_PREFIXES
229 .iter()
230 .find(|(prefix, _)| *prefix == "COP-");
231 assert!(found.is_some(), "Should have COP- prefix");
232 assert_eq!(found.unwrap().1, "github-copilot");
233 }
234
235 #[test]
236 fn test_tool_rule_prefixes_cur() {
237 let found = TOOL_RULE_PREFIXES
239 .iter()
240 .find(|(prefix, _)| *prefix == "CUR-");
241 assert!(found.is_some(), "Should have CUR- prefix");
242 assert_eq!(found.unwrap().1, "cursor");
243 }
244
245 #[test]
246 fn test_get_tool_for_prefix_claude_code() {
247 assert_eq!(get_tool_for_prefix("CC-HK-"), Some("claude-code"));
248 assert_eq!(get_tool_for_prefix("CC-SK-"), Some("claude-code"));
249 assert_eq!(get_tool_for_prefix("CC-AG-"), Some("claude-code"));
250 assert_eq!(get_tool_for_prefix("CC-PL-"), Some("claude-code"));
251 assert_eq!(get_tool_for_prefix("CC-MEM-"), None);
254 }
255
256 #[test]
257 fn test_get_tool_for_prefix_copilot() {
258 assert_eq!(get_tool_for_prefix("COP-"), Some("github-copilot"));
259 }
260
261 #[test]
262 fn test_get_tool_for_prefix_cursor() {
263 assert_eq!(get_tool_for_prefix("CUR-"), Some("cursor"));
264 }
265
266 #[test]
267 fn test_get_tool_for_prefix_generic() {
268 assert_eq!(get_tool_for_prefix("MCP-"), None);
271 assert_eq!(get_tool_for_prefix("XML-"), None);
272 assert_eq!(get_tool_for_prefix("XP-"), None);
273 }
277
278 #[test]
279 fn test_get_tool_for_prefix_unknown() {
280 assert_eq!(get_tool_for_prefix("UNKNOWN-"), None);
281 }
282
283 #[test]
286 fn test_mixed_tool_prefix_as() {
287 assert_eq!(get_tool_for_prefix("AS-"), None);
290 }
291
292 #[test]
293 fn test_mixed_tool_prefix_cc_mem() {
294 assert_eq!(get_tool_for_prefix("CC-MEM-"), None);
298 }
299
300 #[test]
301 fn test_consistent_tool_prefix_cc_hk() {
302 assert_eq!(get_tool_for_prefix("CC-HK-"), Some("claude-code"));
305 }
306
307 #[test]
308 fn test_get_prefixes_for_tool_claude_code() {
309 let prefixes = get_prefixes_for_tool("claude-code");
310 assert!(!prefixes.is_empty());
311 assert!(prefixes.contains(&"CC-HK-"));
312 assert!(prefixes.contains(&"CC-SK-"));
313 assert!(prefixes.contains(&"CC-AG-"));
314 assert!(prefixes.contains(&"CC-PL-"));
315 assert!(!prefixes.contains(&"CC-MEM-"));
318 }
319
320 #[test]
321 fn test_get_prefixes_for_tool_copilot() {
322 let prefixes = get_prefixes_for_tool("github-copilot");
323 assert!(!prefixes.is_empty());
324 assert!(prefixes.contains(&"COP-"));
325 }
326
327 #[test]
328 fn test_get_prefixes_for_tool_cursor() {
329 let prefixes = get_prefixes_for_tool("cursor");
330 assert!(!prefixes.is_empty());
331 assert!(prefixes.contains(&"CUR-"));
332 }
333
334 #[test]
335 fn test_get_prefixes_for_tool_unknown() {
336 let prefixes = get_prefixes_for_tool("unknown-tool");
337 assert!(prefixes.is_empty());
338 }
339
340 #[test]
343 fn test_is_valid_tool_claude_code() {
344 assert!(is_valid_tool("claude-code"));
345 assert!(is_valid_tool("Claude-Code")); assert!(is_valid_tool("CLAUDE-CODE")); }
348
349 #[test]
350 fn test_is_valid_tool_copilot() {
351 assert!(is_valid_tool("github-copilot"));
352 assert!(is_valid_tool("GitHub-Copilot")); }
354
355 #[test]
356 fn test_is_valid_tool_unknown() {
357 assert!(!is_valid_tool("unknown-tool"));
358 assert!(!is_valid_tool(""));
359 }
360
361 #[test]
364 fn test_normalize_tool_name_claude_code() {
365 assert_eq!(normalize_tool_name("claude-code"), Some("claude-code"));
366 assert_eq!(normalize_tool_name("Claude-Code"), Some("claude-code"));
367 assert_eq!(normalize_tool_name("CLAUDE-CODE"), Some("claude-code"));
368 }
369
370 #[test]
371 fn test_normalize_tool_name_copilot() {
372 assert_eq!(
373 normalize_tool_name("github-copilot"),
374 Some("github-copilot")
375 );
376 assert_eq!(
377 normalize_tool_name("GitHub-Copilot"),
378 Some("github-copilot")
379 );
380 }
381
382 #[test]
383 fn test_normalize_tool_name_unknown() {
384 assert_eq!(normalize_tool_name("unknown-tool"), None);
385 assert_eq!(normalize_tool_name(""), None);
386 }
387
388 #[test]
391 fn test_get_prefixes_for_tool_empty_string() {
392 let prefixes = get_prefixes_for_tool("");
394 assert!(
395 prefixes.is_empty(),
396 "Empty string tool should return empty Vec"
397 );
398 }
399
400 #[test]
401 fn test_get_prefixes_for_tool_unknown_tool() {
402 let prefixes = get_prefixes_for_tool("nonexistent-tool");
404 assert!(prefixes.is_empty(), "Unknown tool should return empty Vec");
405 }
406
407 #[test]
408 fn test_get_prefixes_for_tool_claude_code_multiple_prefixes() {
409 let prefixes = get_prefixes_for_tool("claude-code");
411 assert!(
412 prefixes.len() > 1,
413 "claude-code should have multiple prefixes, got {}",
414 prefixes.len()
415 );
416 assert!(
418 prefixes.contains(&"CC-HK-"),
419 "claude-code prefixes should include CC-HK-"
420 );
421 assert!(
422 prefixes.contains(&"CC-SK-"),
423 "claude-code prefixes should include CC-SK-"
424 );
425 }
426
427 #[test]
430 fn test_get_tool_for_prefix_empty_string() {
431 assert_eq!(
433 get_tool_for_prefix(""),
434 None,
435 "Empty prefix should return None"
436 );
437 }
438
439 #[test]
440 fn test_get_tool_for_prefix_unknown_prefix() {
441 assert_eq!(
443 get_tool_for_prefix("NONEXISTENT-"),
444 None,
445 "Unknown prefix should return None"
446 );
447 assert_eq!(
448 get_tool_for_prefix("XX-"),
449 None,
450 "XX- prefix should return None"
451 );
452 }
453
454 #[test]
455 fn test_get_tool_for_prefix_partial_match_not_supported() {
456 assert_eq!(
458 get_tool_for_prefix("CC-"),
459 None,
460 "Partial prefix CC- (without HK/SK/AG) should not match"
461 );
462 assert_eq!(
463 get_tool_for_prefix("C"),
464 None,
465 "Single character should not match"
466 );
467 }
468}