use std::path::Path;
use crate::error::CliError;
use crate::io_util;
pub fn run(rules_dir: &Path) -> Result<(), CliError> {
let transforms = io_util::load_rules(rules_dir)?;
let discipline_n = super::discipline_gate::discipline_len()?;
println!("=== Validating {} ===", rules_dir.display());
println!("Transforms: {}", transforms.len());
println!(
"Discipline: {} rules (DC-01 .. DC-{:02})",
discipline_n, discipline_n
);
println!();
let report = super::discipline_gate::check(&transforms)?;
println!("Nodes: {} (含 branch 内嵌子指令)", report.scanned);
println!();
if report.violations.is_empty() {
println!("[OK] rule-set accepted by the discipline gate");
} else {
for v in &report.violations {
let label = if v.node_type.is_empty() {
"<missing type>"
} else {
v.node_type.as_str()
};
println!(
"[ERROR] {:<24} {:<12} <- {} {}",
v.path, label, v.code, v.reason
);
}
}
println!();
println!("=== Summary ===");
println!("Violations: {}", report.violations.len());
if report.violations.is_empty() {
Ok(())
} else {
Err(CliError::other(format!(
"validation failed with {} violations",
report.violations.len()
)))
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use evorule_tcb::META_INSTRUCTION_TYPES;
#[test]
fn test_whitelist_excludes_g8_words() {
for t in META_INSTRUCTION_TYPES {
assert!(
!matches!(*t, "conditional" | "while_loop" | "sequence"),
"whitelist must not contain G8-forbidden words: {}",
t
);
}
}
#[test]
fn test_whitelist_includes_core_meta_instructions() {
assert!(META_INSTRUCTION_TYPES.contains(&"branch"));
assert!(META_INSTRUCTION_TYPES.contains(&"set"));
assert!(META_INSTRUCTION_TYPES.contains(&"push"));
assert!(META_INSTRUCTION_TYPES.contains(&"io_request"));
assert!(META_INSTRUCTION_TYPES.contains(&"enforce"));
assert!(!META_INSTRUCTION_TYPES.contains(&"collect"));
assert!(!META_INSTRUCTION_TYPES.contains(&"merge"));
assert!(!META_INSTRUCTION_TYPES.contains(&"noop"));
assert!(!META_INSTRUCTION_TYPES.contains(&"increment"));
assert!(!META_INSTRUCTION_TYPES.contains(&"decrement"));
}
}