oratos-audit 0.2.0

Deterministic audit rules for Oratos
Documentation
use std::path::Path;

use oratos_core::{AuditReport, Category, CategoryScores, Finding, PageAudit, PageRef, Severity};
use oratos_html::HtmlPage;

use crate::rules::{all_rules, build_known_paths, AuditContext};
use crate::target::resolve_target;

pub fn audit_pages(target: &str, pages: &[HtmlPage]) -> AuditReport {
    let mut audit_target = resolve_target(target);
    if audit_target.kind == oratos_core::TargetKind::Missing && !pages.is_empty() {
        audit_target.kind = if target.starts_with("http://") || target.starts_with("https://") {
            oratos_core::TargetKind::Url
        } else {
            oratos_core::TargetKind::File
        };
    }
    let site_root = if audit_target.kind == oratos_core::TargetKind::Directory {
        Some(target.to_string())
    } else {
        None
    };

    let known_paths = build_known_paths(pages);

    let has_llms_txt = site_root
        .as_ref()
        .map(|root| Path::new(root).join("llms.txt").exists())
        .unwrap_or(false);

    let ctx = AuditContext {
        site_root,
        known_paths,
        has_llms_txt,
    };

    let rules = all_rules();
    let page_audits: Vec<PageAudit> = pages
        .iter()
        .map(|page| {
            let mut findings = Vec::new();
            for rule in &rules {
                findings.extend(rule.check(page, &ctx));
            }
            let scores = CategoryScores::from_findings(&findings);
            PageAudit {
                page: PageRef {
                    url_or_path: page.url_or_path.clone(),
                    title: page.title.clone(),
                },
                findings,
                scores,
            }
        })
        .collect();

    let mut report = AuditReport::new(audit_target, page_audits);

    if !ctx.has_llms_txt {
        report.findings.push(
            Finding::new(
                "llm.missing-llms-txt",
                Severity::Info,
                Category::LlmReadiness,
                "No llms.txt file found at site root.",
            )
            .with_recommendation("Run `oratos generate llms <target>` to create a draft llms.txt."),
        );
        report.scores = CategoryScores::from_findings(&report.findings);
    }

    report
}