kyyn-core 0.1.10

Core vocabulary for kyyn: registry, links, query AST, plugin and validation contracts for typed, git-backed knowledge bases.
Documentation
//! Host-side progress reporting for long-running plugin work — a FACILITY,
//! not part of the plugin wire contract. Plugins call [`report`] at natural
//! milestones (a page fetched, a file downloaded); the host decides where
//! the lines go. Default: stderr, which every surface tolerates (the CLI
//! shows it live; MCP stdout stays pure JSON-RPC). Future stdio plugins map
//! their stderr onto the same sink — the contract itself stays untouched.

use std::sync::RwLock;

#[allow(clippy::type_complexity)]
static SINK: RwLock<Option<Box<dyn Fn(&str) + Send + Sync>>> = RwLock::new(None);

/// Route progress lines somewhere other than stderr (a UI, a log).
pub fn set_sink(f: impl Fn(&str) + Send + Sync + 'static) {
    *SINK.write().expect("progress sink lock") = Some(Box::new(f));
}

/// One short line of progress ("142 messages (3 pages)…"). Cheap enough to
/// call in loops at coarse milestones; never call per record.
pub fn report(msg: &str) {
    match &*SINK.read().expect("progress sink lock") {
        Some(f) => f(msg),
        None => eprintln!("{msg}"),
    }
}