cordance-llm 0.1.1

Cordance LLM adapters. Bounded candidate prose only. No hard rules from LLM.
Documentation
//! Cordance LLM adapters. Bounded candidate prose only. ADR 0002.
//!
//! LLM output is **never** authority. Every claim must cite a source-ID from the
//! input pack. Hard rules cannot originate here.
//!
//! Enforced at the adapter level:
//! - **Local-only by default.** Non-loopback Ollama URLs are rejected unless
//!   `CORDANCE_ALLOW_REMOTE_LLM=1` is set in the operator's environment.
//! - **Schema-bounded responses.** Outputs deserialise into a fixed
//!   `LlmCandidate` shape; arbitrary text never escapes the adapter.
//! - **4-gram source grounding.** Every claim's text must share at least one
//!   4-gram with a cited source body, else the claim is rejected before the
//!   candidate leaves the adapter.
//! - **Hard rules dropped.** `ClaimType::HardRule` and `ClaimType::ProjectInvariant`
//!   are filtered regardless of grounding — those classes can only come from
//!   doctrine, ADRs, or schemas.
//!
//! # Golden path
//!
//! ```no_run
//! use std::collections::HashMap;
//! use cordance_llm::{OllamaAdapter, OllamaSettings};
//!
//! struct Cfg;
//! impl OllamaSettings for Cfg {
//!     fn base_url(&self) -> &str { "http://localhost:11434" }
//!     fn model(&self) -> &str { "qwen2.5-coder:14b" }
//!     fn temperature(&self) -> f32 { 0.1 }
//!     fn num_ctx(&self) -> u32 { 8192 }
//! }
//!
//! let adapter = OllamaAdapter::from_config(&Cfg);
//! if !adapter.is_available() {
//!     eprintln!("Ollama not reachable at {}", adapter.base_url);
//!     return;
//! }
//!
//! let prompt = "Summarise this project's main purpose in one paragraph.";
//! let cited_ids = vec!["project_readme:README.md".to_string()];
//! let source_bodies: HashMap<String, String> = HashMap::new();
//!
//! let candidate = adapter
//!     .generate_with_grounding(prompt, &cited_ids, &source_bodies)
//!     .expect("ollama generate (grounded)");
//! println!("got {} grounded claims", candidate.claims.len());
//! ```

#![forbid(unsafe_code)]
#![deny(clippy::unwrap_used, clippy::expect_used)]
#![cfg_attr(test, allow(clippy::expect_used, clippy::unwrap_used))]

pub mod candidate;
pub mod ollama;
pub mod prompt;
pub mod validator;

pub use candidate::{ClaimType, LlmCandidate, LlmClaim};
pub use ollama::{LlmError, OllamaAdapter, OllamaSettings};
pub use validator::{validate, validate_with_sources};