hunyi 0.6.0

渾儀 (Hunyi) — Tianheng's semantic (AST/syn) observation dimension, the complement of the static import boundary. Declare in Rust how a module's public surface must behave: what its API must not expose (types — including named public re-exports and, opt-in, a trait impl's impl-site positions — and no dyn / impl Trait or async fn seam), where a trait may be implemented, that it declares no bare pub, and which markers a type must not acquire — observed via syn, reacted in CI. The heavy syn dependency is quarantined here, never in the core.
Documentation
//! 渾儀 as an observation participant.

use std::path::Path;

use xuanji::{BoundDecl, Observer, Outcome};

use crate::{SemanticBoundaries, check_all, observation_bounds};

/// The semantic dimension as an [`Observer`].
#[derive(Debug, Clone)]
pub struct SemanticObserver {
    boundaries: SemanticBoundaries,
}

impl SemanticObserver {
    /// Observe with the given semantic boundary bundle.
    pub fn new(boundaries: SemanticBoundaries) -> Self {
        Self { boundaries }
    }
}

impl Observer for SemanticObserver {
    /// Delegates to the dimension's composed entry point, which reads its own workspace metadata
    /// when boundaries require observation — 三儀 ⊥ 三儀: no scanner is threaded in from a sibling.
    fn observe(&self, manifest_path: &Path) -> Outcome {
        check_all(&self.boundaries, manifest_path)
    }

    fn bounds(&self) -> Vec<BoundDecl> {
        observation_bounds()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn empty_boundaries_are_clean_without_reading_a_manifest() {
        let observer = SemanticObserver::new(SemanticBoundaries::default());
        let absent = std::env::temp_dir().join(format!(
            "tianheng-empty-semantic-observer-{}-does-not-exist/Cargo.toml",
            std::process::id()
        ));

        assert!(matches!(observer.observe(&absent), Outcome::Clean(_)));
    }
}