osp-cli 1.5.1

CLI and REPL for querying and managing OSP infrastructure data
Documentation
#![allow(missing_docs)]

use std::fs;
use std::path::PathBuf;

fn workspace_root() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}

fn read_source(relative: &str) -> String {
    let path = workspace_root().join(relative);
    fs::read_to_string(&path)
        .unwrap_or_else(|err| panic!("failed to read {}: {err}", path.display()))
}

#[test]
fn app_runtime_rebuild_flows_share_the_host_assembly_boundary() {
    let host = read_source("src/app/host.rs");
    let rebuild = read_source("src/app/rebuild.rs");
    let lifecycle = read_source("src/app/repl_lifecycle.rs");

    assert!(
        host.contains("prepare_startup_host("),
        "host startup should route startup assembly through the bootstrap/assembly seam"
    );
    assert!(
        !host.contains("PluginManager::new(") && !host.contains("load_theme_catalog(&config)"),
        "host startup should not reassemble plugins/themes ad hoc"
    );
    assert!(
        rebuild.contains("ResolvedHostInputs::derive("),
        "rebuild path should derive runtime inputs through app::assembly"
    );
    assert!(
        !rebuild.contains("PluginManager::new(")
            && !rebuild.contains("load_theme_catalog(&config)")
            && !rebuild.contains("init_developer_logging(")
            && !rebuild.contains("build_logging_config("),
        "rebuild path drifted back into ad hoc assembly or side effects"
    );
    assert!(
        lifecycle.contains("apply_runtime_side_effects("),
        "rebuild lifecycle should own post-rebuild side effects"
    );
}

#[test]
fn ui_runtime_rendering_flows_through_plan_lower_emit_without_document_fallbacks() {
    let settings = read_source("src/ui/settings/mod.rs");
    let plan = read_source("src/ui/plan/mod.rs");
    let lower = read_source("src/ui/lower.rs");
    let emit = read_source("src/ui/emit/mod.rs");
    let ui = read_source("src/ui/mod.rs");

    assert!(
        settings.contains("pub struct ResolvedRenderSettings")
            && settings.contains("pub fn resolve_render_settings(&self) -> ResolvedRenderSettings")
            && settings.contains(
                "pub(crate) fn resolve_output_format(&self, output: &OutputResult) -> OutputFormat"
            ),
        "ui settings should own resolved render facts and output-format resolution"
    );
    assert!(
        plan.contains("pub fn plan_output(")
            && plan.contains("let format = settings.resolve_output_format(output);"),
        "ui planning should own the semantic render plan entrypoint"
    );
    assert!(
        lower.contains("fn lower_output(") && lower.contains("RenderPlan"),
        "ui lowering should consume the semantic render plan"
    );
    assert!(
        emit.contains("pub fn emit_doc(") && emit.contains("OutputFormat::Markdown"),
        "ui emitter should stay behind a single document emission seam"
    );
    assert!(
        ui.contains("render_output_with_profile(")
            && ui.contains("plan_output(output, settings, profile)")
            && ui.contains("emit::emit_doc(")
            && ui.contains("&lower::lower_output(output, &plan)")
            && ui.contains("plan.format")
            && ui.contains("&plan.settings")
            && !ui.contains("document_render::render_document(")
            && !ui.contains("build_document_from_output_plan"),
        "ui runtime should flow through the canonical plan/lower/emit path without document fallback"
    );
}

#[test]
fn plugin_manager_routes_read_paths_through_the_active_view() {
    let active = read_source("src/plugin/active.rs");
    let manager = read_source("src/plugin/manager.rs");
    let catalog = read_source("src/plugin/catalog.rs");

    assert!(
        active.contains("struct ActivePluginView"),
        "plugin layer should keep the shared active working set"
    );
    assert!(
        manager.contains("with_passive_view") && manager.contains("with_dispatch_view"),
        "plugin manager should route operations through shared view helpers"
    );
    assert!(
        !manager.contains("let healthy = healthy_plugins")
            && !manager.contains("healthy_plugins(discovered")
            && !manager.contains("resolve_provider_for_command(")
            && !manager.contains("provider_labels_by_command("),
        "plugin manager drifted back into ad hoc health/provider derivation"
    );
    assert!(
        catalog.contains("ActivePluginView"),
        "catalog building should consume the shared active-plugin view"
    );
}

#[test]
fn repl_engine_keeps_host_facing_config_types_out_of_editor_orchestration() {
    let engine = read_source("src/repl/engine.rs");
    let config = read_source("src/repl/engine/config.rs");

    assert!(
        engine.contains("mod config;") && engine.contains("pub use config::{"),
        "engine should re-export host-facing REPL config from a dedicated module"
    );
    assert!(
        !engine.contains("pub struct ReplRunConfig")
            && !engine.contains("pub struct ReplAppearance")
            && !engine.contains("pub enum ReplRunResult"),
        "engine.rs still owns host-facing REPL config/outcome types directly"
    );
    assert!(
        config.contains("pub struct ReplRunConfig")
            && config.contains("pub struct ReplAppearance")
            && config.contains("pub enum ReplRunResult"),
        "repl engine config surface should live in repl/engine/config.rs"
    );
}