ailint-core 1.0.1

Core library for ailint: discovery, parsing, rule engine, and reporters for AI agent guidance files.
Documentation
use std::path::PathBuf;

use ailint_core::{lint, Config};

fn fixture(rule: &str, kind: &str) -> PathBuf {
    let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    p.push("tests/fixtures/semantic");
    p.push(rule);
    p.push(kind);
    p.push("AGENTS.md");
    p
}

fn count(vs: &[ailint_core::Violation], code: u16) -> usize {
    vs.iter().filter(|v| v.rule_id.code == code).count()
}

#[test]
fn ail100_fires_on_vague_phrase() {
    let path = fixture("vague_instruction", "bad");
    let violations = lint(&path, &Config::default()).unwrap();
    assert!(
        count(&violations, 100) >= 1,
        "expected AIL100 to fire, got {violations:?}"
    );
}

#[test]
fn ail100_silent_on_concrete_list() {
    let path = fixture("vague_instruction", "good");
    let violations = lint(&path, &Config::default()).unwrap();
    assert_eq!(
        count(&violations, 100),
        0,
        "expected AIL100 silent, got {violations:?}"
    );
}

#[test]
fn ail101_fires_when_examples_section_lacks_code_or_eg() {
    let path = fixture("missing_examples", "bad");
    let violations = lint(&path, &Config::default()).unwrap();
    assert!(
        count(&violations, 101) >= 1,
        "expected AIL101 to fire, got {violations:?}"
    );
}

#[test]
fn ail101_silent_when_section_has_code_block() {
    let path = fixture("missing_examples", "good");
    let violations = lint(&path, &Config::default()).unwrap();
    assert_eq!(
        count(&violations, 101),
        0,
        "expected AIL101 silent, got {violations:?}"
    );
}

#[test]
fn ail102_fires_on_long_list_item() {
    let path = fixture("excessive_length", "bad");
    let violations = lint(&path, &Config::default()).unwrap();
    assert!(
        count(&violations, 102) >= 1,
        "expected AIL102 to fire, got {violations:?}"
    );
}

#[test]
fn ail102_silent_on_short_list_items() {
    let path = fixture("excessive_length", "good");
    let violations = lint(&path, &Config::default()).unwrap();
    assert_eq!(
        count(&violations, 102),
        0,
        "expected AIL102 silent, got {violations:?}"
    );
}

#[test]
fn ail103_fires_on_duplicate_list_items() {
    let path = fixture("duplicate_rules", "bad");
    let violations = lint(&path, &Config::default()).unwrap();
    assert!(
        count(&violations, 103) >= 1,
        "expected AIL103 to fire, got {violations:?}"
    );
}

#[test]
fn ail103_silent_on_distinct_list_items() {
    let path = fixture("duplicate_rules", "good");
    let violations = lint(&path, &Config::default()).unwrap();
    assert_eq!(
        count(&violations, 103),
        0,
        "expected AIL103 silent, got {violations:?}"
    );
}

#[test]
fn ail104_fires_on_negative_overload() {
    let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    p.push("tests/fixtures/semantic/negative_constraint_overload/bad/AGENTS.md");
    let violations = lint(&p, &Config::default()).unwrap();
    assert!(
        count(&violations, 104) >= 1,
        "expected AIL104 to fire, got {violations:?}"
    );
}

#[test]
fn ail104_silent_on_positive_guidance() {
    let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    p.push("tests/fixtures/semantic/negative_constraint_overload/good/AGENTS.md");
    let violations = lint(&p, &Config::default()).unwrap();
    assert_eq!(
        count(&violations, 104),
        0,
        "expected AIL104 silent, got {violations:?}"
    );
}

#[test]
fn ail105_fires_on_no_tags() {
    let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    p.push("tests/fixtures/semantic/vendor_optimization_syntax/bad/CLAUDE.md");
    let violations = lint(&p, &Config::default()).unwrap();
    assert!(
        count(&violations, 105) >= 1,
        "expected AIL105 to fire, got {violations:?}"
    );
}

#[test]
fn ail105_silent_with_tags() {
    let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    p.push("tests/fixtures/semantic/vendor_optimization_syntax/good/CLAUDE.md");
    let violations = lint(&p, &Config::default()).unwrap();
    assert_eq!(
        count(&violations, 105),
        0,
        "expected AIL105 silent, got {violations:?}"
    );
}