Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

src/docs/mod.rs

//! Documentation assembly.
//!
//! nornir owns generated docs: README.md, CLAUDE.md, CHANGELOG.md.
//! Files (or sections) carrying the marker `<!-- nornir:generated -->`
//! are rewritten in full on each release from:
//!
//!  1. the per-repo TOML in `workspace_holger/release/`
//!  2. the latest [`BenchRun`]
//!  3. the bench history file
//!
//! Hand-written docs without the marker are only spot-checked by
//! [`assemble_and_check`] (version string + headline must match).
//!
//! ## Where the code lives now
//!
//! The warehouse-free ASSEMBLY half — the `nornir:gen:*` marker engine and
//! every section renderer (`sections`), the managed-docs layout (`layout`),
//! the book assembler (`book`), `bench_status`, `test_inventory`, `hooks`,
//! `pages`, the typst `svg` charts, `apidoc` and `ai_context` — lives in the
//! [`nornir_docgen`] edda leaf (which re-exports [`nornir_docbook`]'s render
//! engine as its `export`). This module re-exports it all **byte-for-byte
//! path-unchanged**, exactly like the existing `docs::export` shim, so every
//! `docs::…` call site (the CLI, the MCP server, the release gates, the tests)
//! resolves as before.
//!
//! What genuinely stays here is Mímir: [`warehouse`] (the Iceberg `doc_exports`
//! table), [`search`] (the tantivy docs index) and [`release_book`] — they read
//! the warehouse. The `depgraph` section's network half (clone missing sibling
//! path-dep repos) is injected into [`Ctx`] via
//! [`Ctx::with_path_dep_cloner`] — nornir's callers pass
//! [`crate::security::clone_missing_path_dep_siblings`] through
//! [`crate::security::docs_path_dep_cloner`]; the leaf itself stays
//! network-free.

use std::path::Path;

use anyhow::{Result, anyhow};

use crate::bench::BenchRun;

pub const GENERATED_MARKER: &str = "<!-- nornir:generated -->";

pub use nornir_docgen::sections;
pub use sections::{Ctx, FileReport, assemble_file, check_file};

pub use bench_status::{
    HarnessProbe, PREVIEW_TOKEN, is_preview, probe_harness, suspect_reasons, suspects,
};
pub use nornir_docgen::bench_status;

pub use nornir_docgen::test_inventory;
pub use test_inventory::{TestInventory, scan as scan_tests, scan_opts as scan_tests_opts};

pub use hooks::{InstallReport, install_hooks, pre_commit_script};
pub use nornir_docgen::hooks;

pub use layout::{
    DocCheckRow, DocsRenderCfg, MANAGED_DOCS, RenderReport, RepoLayout,
    check_all as render_check_all, check_all_status, check_doc as render_check_doc, check_outcome,
    init_repo,
};
pub use nornir_docgen::layout;

/// The `nornir:claim:` gate — RE-EXPORTED from the engine, not owned here.
///
/// ⚠ MOVED OUT 2026-08-27, and the move is the fix. This module used to own
/// `claims.rs` and wrap six engine functions with it. That gated NORNIR's road
/// and nothing else, and nornir is not the only road: `dwarves` depends on
/// `nornir-docgen` directly and calls [`layout::render_all`],
/// [`layout::render_sources_in_place`], [`book::build_book`],
/// [`book::build_books`] and [`book::build_book_svg_pages`] raw from
/// `dwarves/crates/dwarves/src/docs/mod.rs` — and `gate_doc_claims` had ZERO
/// occurrences in the whole dwarves tree. So the FORGE, the thing that actually
/// burns the books, took the ungated road, and a `dwarves docs render` on a
/// falsified claim was measured writing the artifact with exit 0 and stamping
/// `⚠ GENERATED … DO NOT EDIT` over the contradicted sentence.
///
/// The gate now lives on the engine's own entry points, so BOTH roads pass
/// through one writer, and the six wrappers that used to live here are GONE —
/// a wrapper that adds nothing is a second copy of the rule (L5). Every
/// `docs::render_doc` / `render_all` / `render_sources_in_place` /
/// `build_book*` call site in nornir resolves to the gated engine function by
/// re-export, unchanged.
pub use nornir_docgen::claims;
pub use claims::{Claim, ClaimKind, gate_repo as gate_doc_claims};

pub use layout::{render_all, render_doc, render_sources_in_place};

pub mod warehouse;
pub use warehouse::{
    DocExport, DocHistoryRow, ExportFilter, history_outcome as history_export_outcome,
    list_doc_exports, list_doc_exports_async, record_doc_export, record_doc_export_async,
};

pub mod search;
pub use search::{
    build_docs_index, discover_doc_files, docs_index_dir, restore_docs_index, search_docs,
    snapshot_docs_index,
};

pub use nornir_docgen::pages;
pub use pages::{PAGES_BRANCH, PagesBuild, force_push_cmd, index_html, stage_pages_branch};

#[cfg(feature = "docs-export")]
pub mod export;
#[cfg(feature = "docs-export")]
pub use export::{DocFormat, ExportMeta, export as export_bytes, export_repo};

#[cfg(feature = "docs-export")]
pub use nornir_docgen::svg;

#[cfg(feature = "docs-export")]
pub mod release_book;
#[cfg(feature = "docs-export")]
pub use release_book::{BookOptions, build_release_book, build_release_book_markdown};

#[cfg(feature = "docs-export")]
pub use book::{
    BookRoute, BuiltBook, Chapter, classify, collect_chapters, default_routing, load_routing,
    resolve_version,
};
#[cfg(feature = "docs-export")]
pub use nornir_docgen::book;

/// The book builders — gated inside the engine now (see the [`claims`] note
/// above), so these are plain re-exports and no longer wrappers. The book binds
/// every `.nornir/*.md` source into one publication, so a false claimed sentence
/// in any of them refuses the whole build.
#[cfg(feature = "docs-export")]
pub use book::{build_book, build_book_svg_pages, build_books};

#[cfg(feature = "docs-export")]
pub use nornir_docgen::apidoc;

#[cfg(feature = "docs-export")]
pub use ai_context::{
    AiContextOpts, AiContextOutput, BuildStats, GenMode, build as build_ai_context,
};
#[cfg(feature = "docs-export")]
pub use nornir_docgen::ai_context;

/// Gate 7: docs are in sync with the run. For generated files, rewrite
/// and verify. For hand-written README.md (no marker), require that
/// `v<version>` appears in the file — but only when a bench run is available
/// to name the version. A repo with no recorded bench run (e.g. nornir itself,
/// which ships no `nornir-bench.rs`) still passes the gate via its generated
/// README; the bench run is not a docs-freshness prerequisite.
pub fn assemble_and_check(repo_root: &Path, run: Option<&BenchRun>) -> Result<()> {
    let readme = repo_root.join("README.md");
    let text = std::fs::read_to_string(&readme).unwrap_or_default();
    if text.contains(GENERATED_MARKER) {
        return Ok(());
    }
    // Hand-written README: assert the headline names the release version, but
    // only when a bench run gives us a version to look for. No run ⇒ nothing to
    // assert here (freshness of the prose is checked by `check_file` above).
    if let Some(run) = run {
        let needle = format!("v{}", run.version);
        if !text.contains(&needle) {
            return Err(anyhow!(
                "README.md does not mention {needle} — update headline before release"
            ));
        }
    }
    Ok(())
}