spec-drift 0.1.0

Semantic coherence analysis between specification and implementation for Rust projects.
Documentation
//! `spec-drift` — semantic coherence analysis between a project's
//! specification surfaces (README, AGENTS.md, examples, CI) and its Rust code.
//!
//! The library exposes the domain model, analyzers, and reporters so they can
//! be embedded in editors or other tools. The `spec-drift` binary is a thin
//! CLI wrapper over [`run`].

pub mod analyzers;
pub mod baseline;
pub mod blame;
pub mod config;
pub mod context;
pub mod domain;
pub mod error;
pub mod llm;
pub mod parsers;
pub mod reporters;
pub mod sources;
pub mod suppress;
pub mod workspace;

pub use config::Config;
pub use context::ProjectContext;
pub use domain::{
    Attribution, ClaimKind, CodeFact, Confidence, Divergence, FactKind, Location, RuleId,
    Severity, SpecClaim,
};
pub use error::SpecDriftError;

use analyzers::DriftAnalyzer;
use rayon::prelude::*;

/// Execute every analyzer in parallel and return divergences sorted
/// deterministically by `(file, line, rule)` so output can be diffed between
/// runs.
///
/// Analyzers are independent by construction — see the `DriftAnalyzer` trait
/// docstring — so `par_iter` is safe and parallelizes nicely on multi-pillar
/// runs where one analyzer (e.g. `ExamplesAnalyzer`) spends most of its time
/// blocked on `cargo`.
pub fn run(ctx: &ProjectContext, analyzers: &[Box<dyn DriftAnalyzer>]) -> Vec<Divergence> {
    let mut all: Vec<Divergence> = analyzers
        .par_iter()
        .flat_map_iter(|a| a.analyze(ctx))
        .collect();

    all.sort_by(|a, b| {
        a.location
            .file
            .cmp(&b.location.file)
            .then_with(|| a.location.line.cmp(&b.location.line))
            .then_with(|| a.rule.as_str().cmp(b.rule.as_str()))
    });
    all
}

/// Apply a [`Config`] to a divergence set: drop suppressed items and apply
/// severity overrides.
pub fn apply_config(
    mut divs: Vec<Divergence>,
    cfg: &Config,
    root: &std::path::Path,
) -> Vec<Divergence> {
    divs.retain(|d| !cfg.is_suppressed(d, root));
    cfg.apply_severity_overrides(&mut divs);
    divs
}

/// Promote every non-deterministic divergence one severity level. Mirrors the
/// `--strict` CLI flag — deterministic rules stay untouched because they
/// already carry unambiguous verdicts.
pub fn apply_strict(divs: &mut [Divergence]) {
    for d in divs.iter_mut() {
        if d.rule.confidence() != Confidence::Deterministic {
            d.severity = d.severity.promoted();
        }
    }
}