agentlint-cli 0.1.0

A fast, local-first linter for AI coding-agent configuration
Documentation
//! Reusable analysis engine for `agentlint`.

pub mod analysis;
pub mod config;
pub mod diagnostic;
pub mod fix;
pub mod output;
pub mod project;
pub mod rules;
pub mod scanner;

use std::path::{Path, PathBuf};

use anyhow::Result;
use serde::Serialize;

pub use config::{Config, SeveritySetting};
pub use diagnostic::{Diagnostic, Severity, Span};
pub use rules::{RuleCategory, RuleDefinition};

/// Options supplied by front ends embedding the linter.
#[derive(Debug, Clone, Default)]
pub struct LintOptions {
    pub config_path: Option<PathBuf>,
    pub extra_ignores: Vec<String>,
    pub minimum_severity: Option<Severity>,
}

/// Stable machine-readable result returned by the analysis engine.
#[derive(Debug, Clone, Serialize)]
pub struct LintReport {
    pub version: &'static str,
    pub root: PathBuf,
    pub files_scanned: usize,
    pub estimated_total_tokens: usize,
    pub estimated_duplicated_tokens: usize,
    pub diagnostics: Vec<Diagnostic>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub fixed_files: Vec<PathBuf>,
}

impl LintReport {
    pub fn error_count(&self) -> usize {
        self.diagnostics
            .iter()
            .filter(|item| item.severity == Severity::Error)
            .count()
    }

    pub fn warning_count(&self) -> usize {
        self.diagnostics
            .iter()
            .filter(|item| item.severity == Severity::Warning)
            .count()
    }
}

/// Scan and analyze a repository without executing any project code.
///
/// # Errors
///
/// Returns an error when the root cannot be resolved, configuration is invalid, or a
/// relevant source file cannot be read safely.
pub fn lint(root: &Path, options: &LintOptions) -> Result<LintReport> {
    let root = root.canonicalize()?;
    let config = Config::load(&root, options.config_path.as_deref())?;
    let project = project::ProjectFacts::inspect(&root)?;
    let files = scanner::scan(&root, &config, &options.extra_ignores)?;
    Ok(analysis::analyze(
        &root,
        &files,
        &project,
        &config,
        options.minimum_severity,
    ))
}