include!(concat!(env!("OUT_DIR"), "/rules_data.rs"));
pub fn rule_count() -> usize {
RULES_DATA.len()
}
pub fn get_rule_name(id: &str) -> Option<&'static str> {
RULES_DATA
.iter()
.find(|(rule_id, _)| *rule_id == id)
.map(|(_, name)| *name)
}
pub fn valid_tools() -> &'static [&'static str] {
VALID_TOOLS
}
pub fn authoring_families() -> &'static [&'static str] {
AUTHORING_FAMILIES
}
pub fn authoring_catalog_json() -> &'static str {
AUTHORING_CATALOG_JSON
}
pub fn get_rule_metadata(id: &str) -> Option<(&'static str, &'static str, &'static str)> {
RULES_METADATA
.iter()
.find(|(rule_id, _, _, _)| *rule_id == id)
.map(|(_, category, severity, tool)| (*category, *severity, *tool))
}
pub fn get_tool_for_prefix(prefix: &str) -> Option<&'static str> {
TOOL_RULE_PREFIXES
.iter()
.find(|(p, _)| *p == prefix)
.map(|(_, tool)| *tool)
}
pub fn get_prefixes_for_tool(tool: &str) -> Vec<&'static str> {
TOOL_RULE_PREFIXES
.iter()
.filter(|(_, t)| t.eq_ignore_ascii_case(tool))
.map(|(prefix, _)| *prefix)
.collect()
}
pub fn is_valid_tool(tool: &str) -> bool {
VALID_TOOLS.iter().any(|t| t.eq_ignore_ascii_case(tool))
}
pub fn normalize_tool_name(tool: &str) -> Option<&'static str> {
VALID_TOOLS
.iter()
.find(|t| t.eq_ignore_ascii_case(tool))
.copied()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[allow(clippy::const_is_empty)]
fn test_rules_data_not_empty() {
assert!(!RULES_DATA.is_empty(), "RULES_DATA should not be empty");
}
#[test]
fn test_rule_count() {
assert_eq!(rule_count(), RULES_DATA.len());
}
#[test]
fn test_get_rule_name_exists() {
let name = get_rule_name("AS-001");
assert!(name.is_some(), "AS-001 should exist");
}
#[test]
fn test_get_rule_name_not_exists() {
let name = get_rule_name("NONEXISTENT-999");
assert!(name.is_none(), "Nonexistent rule should return None");
}
#[test]
fn test_no_duplicate_ids() {
let mut ids: Vec<&str> = RULES_DATA.iter().map(|(id, _)| *id).collect();
let original_len = ids.len();
ids.sort();
ids.dedup();
assert_eq!(ids.len(), original_len, "Should have no duplicate rule IDs");
}
#[test]
#[allow(clippy::const_is_empty)]
fn test_rules_metadata_not_empty() {
assert!(
!RULES_METADATA.is_empty(),
"RULES_METADATA should not be empty"
);
}
#[test]
fn test_rules_metadata_same_length_as_rules_data() {
assert_eq!(
RULES_METADATA.len(),
RULES_DATA.len(),
"RULES_METADATA and RULES_DATA should have the same number of entries"
);
}
#[test]
fn test_get_rule_metadata_as_001() {
let meta = get_rule_metadata("AS-001");
assert!(meta.is_some(), "AS-001 should have metadata");
let (category, severity, _tool) = meta.unwrap();
assert_eq!(category, "agent-skills");
assert_eq!(severity, "HIGH");
}
#[test]
fn test_get_rule_metadata_cc_hk_001() {
let meta = get_rule_metadata("CC-HK-001");
assert!(meta.is_some(), "CC-HK-001 should have metadata");
let (category, severity, tool) = meta.unwrap();
assert!(!category.is_empty(), "category should not be empty");
assert!(!severity.is_empty(), "severity should not be empty");
assert_eq!(tool, "claude-code");
}
#[test]
fn test_get_rule_metadata_nonexistent() {
let meta = get_rule_metadata("NONEXISTENT-999");
assert!(meta.is_none(), "Nonexistent rule should return None");
}
#[test]
fn test_get_rule_metadata_tool_may_be_empty() {
let meta = get_rule_metadata("AS-001");
assert!(meta.is_some());
let (_category, _severity, tool) = meta.unwrap();
assert_eq!(tool, "", "AS-001 should have empty tool (generic rule)");
}
#[test]
#[allow(clippy::const_is_empty)]
fn test_valid_tools_not_empty() {
assert!(!VALID_TOOLS.is_empty(), "VALID_TOOLS should not be empty");
}
#[test]
fn test_valid_tools_contains_claude_code() {
assert!(
VALID_TOOLS.contains(&"claude-code"),
"VALID_TOOLS should contain 'claude-code'"
);
}
#[test]
fn test_valid_tools_contains_github_copilot() {
assert!(
VALID_TOOLS.contains(&"github-copilot"),
"VALID_TOOLS should contain 'github-copilot'"
);
}
#[test]
fn test_valid_tools_contains_cursor() {
assert!(
VALID_TOOLS.contains(&"cursor"),
"VALID_TOOLS should contain 'cursor'"
);
}
#[test]
fn test_valid_tools_helper() {
let tools = valid_tools();
assert!(!tools.is_empty());
assert!(tools.contains(&"claude-code"));
}
#[test]
#[allow(clippy::const_is_empty)]
fn test_authoring_families_not_empty() {
assert!(
!AUTHORING_FAMILIES.is_empty(),
"AUTHORING_FAMILIES should not be empty"
);
}
#[test]
fn test_authoring_families_contains_core_families() {
let families = authoring_families();
assert!(families.contains(&"skill"));
assert!(families.contains(&"agent"));
assert!(families.contains(&"hooks"));
assert!(families.contains(&"mcp"));
}
#[test]
fn test_authoring_catalog_json_is_valid_json() {
let parsed: serde_json::Value = serde_json::from_str(authoring_catalog_json())
.expect("AUTHORING_CATALOG_JSON should be valid JSON");
assert!(
parsed.is_object(),
"authoring catalog should be a JSON object"
);
}
#[test]
#[allow(clippy::const_is_empty)]
fn test_tool_rule_prefixes_not_empty() {
assert!(
!TOOL_RULE_PREFIXES.is_empty(),
"TOOL_RULE_PREFIXES should not be empty"
);
}
#[test]
fn test_tool_rule_prefixes_cc_hk() {
let found = TOOL_RULE_PREFIXES
.iter()
.find(|(prefix, _)| *prefix == "CC-HK-");
assert!(found.is_some(), "Should have CC-HK- prefix");
assert_eq!(found.unwrap().1, "claude-code");
}
#[test]
fn test_tool_rule_prefixes_cop() {
let found = TOOL_RULE_PREFIXES
.iter()
.find(|(prefix, _)| *prefix == "COP-");
assert!(found.is_some(), "Should have COP- prefix");
assert_eq!(found.unwrap().1, "github-copilot");
}
#[test]
fn test_tool_rule_prefixes_cur() {
let found = TOOL_RULE_PREFIXES
.iter()
.find(|(prefix, _)| *prefix == "CUR-");
assert!(found.is_some(), "Should have CUR- prefix");
assert_eq!(found.unwrap().1, "cursor");
}
#[test]
fn test_get_tool_for_prefix_claude_code() {
assert_eq!(get_tool_for_prefix("CC-HK-"), Some("claude-code"));
assert_eq!(get_tool_for_prefix("CC-SK-"), Some("claude-code"));
assert_eq!(get_tool_for_prefix("CC-AG-"), Some("claude-code"));
assert_eq!(get_tool_for_prefix("CC-PL-"), Some("claude-code"));
assert_eq!(get_tool_for_prefix("CC-MEM-"), None);
}
#[test]
fn test_get_tool_for_prefix_copilot() {
assert_eq!(get_tool_for_prefix("COP-"), Some("github-copilot"));
}
#[test]
fn test_get_tool_for_prefix_cursor() {
assert_eq!(get_tool_for_prefix("CUR-"), Some("cursor"));
}
#[test]
fn test_get_tool_for_prefix_generic() {
assert_eq!(get_tool_for_prefix("MCP-"), None);
assert_eq!(get_tool_for_prefix("XML-"), None);
assert_eq!(get_tool_for_prefix("XP-"), None);
}
#[test]
fn test_get_tool_for_prefix_unknown() {
assert_eq!(get_tool_for_prefix("UNKNOWN-"), None);
}
#[test]
fn test_mixed_tool_prefix_as() {
assert_eq!(get_tool_for_prefix("AS-"), None);
}
#[test]
fn test_mixed_tool_prefix_cc_mem() {
assert_eq!(get_tool_for_prefix("CC-MEM-"), None);
}
#[test]
fn test_consistent_tool_prefix_cc_hk() {
assert_eq!(get_tool_for_prefix("CC-HK-"), Some("claude-code"));
}
#[test]
fn test_get_prefixes_for_tool_claude_code() {
let prefixes = get_prefixes_for_tool("claude-code");
assert!(!prefixes.is_empty());
assert!(prefixes.contains(&"CC-HK-"));
assert!(prefixes.contains(&"CC-SK-"));
assert!(prefixes.contains(&"CC-AG-"));
assert!(prefixes.contains(&"CC-PL-"));
assert!(!prefixes.contains(&"CC-MEM-"));
}
#[test]
fn test_get_prefixes_for_tool_copilot() {
let prefixes = get_prefixes_for_tool("github-copilot");
assert!(!prefixes.is_empty());
assert!(prefixes.contains(&"COP-"));
}
#[test]
fn test_get_prefixes_for_tool_cursor() {
let prefixes = get_prefixes_for_tool("cursor");
assert!(!prefixes.is_empty());
assert!(prefixes.contains(&"CUR-"));
}
#[test]
fn test_get_prefixes_for_tool_unknown() {
let prefixes = get_prefixes_for_tool("unknown-tool");
assert!(prefixes.is_empty());
}
#[test]
fn test_is_valid_tool_claude_code() {
assert!(is_valid_tool("claude-code"));
assert!(is_valid_tool("Claude-Code")); assert!(is_valid_tool("CLAUDE-CODE")); }
#[test]
fn test_is_valid_tool_copilot() {
assert!(is_valid_tool("github-copilot"));
assert!(is_valid_tool("GitHub-Copilot")); }
#[test]
fn test_is_valid_tool_unknown() {
assert!(!is_valid_tool("unknown-tool"));
assert!(!is_valid_tool(""));
}
#[test]
fn test_normalize_tool_name_claude_code() {
assert_eq!(normalize_tool_name("claude-code"), Some("claude-code"));
assert_eq!(normalize_tool_name("Claude-Code"), Some("claude-code"));
assert_eq!(normalize_tool_name("CLAUDE-CODE"), Some("claude-code"));
}
#[test]
fn test_normalize_tool_name_copilot() {
assert_eq!(
normalize_tool_name("github-copilot"),
Some("github-copilot")
);
assert_eq!(
normalize_tool_name("GitHub-Copilot"),
Some("github-copilot")
);
}
#[test]
fn test_normalize_tool_name_unknown() {
assert_eq!(normalize_tool_name("unknown-tool"), None);
assert_eq!(normalize_tool_name(""), None);
}
#[test]
fn test_get_prefixes_for_tool_empty_string() {
let prefixes = get_prefixes_for_tool("");
assert!(
prefixes.is_empty(),
"Empty string tool should return empty Vec"
);
}
#[test]
fn test_get_prefixes_for_tool_unknown_tool() {
let prefixes = get_prefixes_for_tool("nonexistent-tool");
assert!(prefixes.is_empty(), "Unknown tool should return empty Vec");
}
#[test]
fn test_get_prefixes_for_tool_claude_code_multiple_prefixes() {
let prefixes = get_prefixes_for_tool("claude-code");
assert!(
prefixes.len() > 1,
"claude-code should have multiple prefixes, got {}",
prefixes.len()
);
assert!(
prefixes.contains(&"CC-HK-"),
"claude-code prefixes should include CC-HK-"
);
assert!(
prefixes.contains(&"CC-SK-"),
"claude-code prefixes should include CC-SK-"
);
}
#[test]
fn test_get_tool_for_prefix_empty_string() {
assert_eq!(
get_tool_for_prefix(""),
None,
"Empty prefix should return None"
);
}
#[test]
fn test_get_tool_for_prefix_unknown_prefix() {
assert_eq!(
get_tool_for_prefix("NONEXISTENT-"),
None,
"Unknown prefix should return None"
);
assert_eq!(
get_tool_for_prefix("XX-"),
None,
"XX- prefix should return None"
);
}
#[test]
fn test_get_tool_for_prefix_partial_match_not_supported() {
assert_eq!(
get_tool_for_prefix("CC-"),
None,
"Partial prefix CC- (without HK/SK/AG) should not match"
);
assert_eq!(
get_tool_for_prefix("C"),
None,
"Single character should not match"
);
}
}