wenlan-types 0.9.5

Shared wire-format types for Wenlan, the local-first personal agent memory system.
Documentation
// SPDX-License-Identifier: Apache-2.0
//! Lightweight plugin distribution contract tests.

use std::fs;
use std::path::{Path, PathBuf};

fn repo_root() -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .ancestors()
        .nth(2)
        .expect("wenlan-types is nested under crates/")
        .to_path_buf()
}

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

#[test]
fn plugin_init_repairs_stale_daemon_versions() {
    let init = read_text("plugin/skills/init/SKILL.md");
    let hook = read_text("plugin/hooks/check-daemon.sh");

    for needle in [
        "Compare daemon version vs plugin manifest version",
        "If mismatch, repair the runtime",
        "curl -fsSL https://raw.githubusercontent.com/7xuanlu/wenlan/v${EXPECTED_VER}/install.sh | bash",
        "export PATH=\"$HOME/.wenlan/bin:$PATH\" && wenlan setup --basic && wenlan install",
    ] {
        assert!(
            init.contains(needle),
            "/init missing stale-daemon repair contract: {needle}"
        );
    }

    assert!(
        hook.contains("Run /wenlan:init to repair"),
        "SessionStart hook should direct version mismatches to the self-healing /init entrypoint"
    );
    assert!(
        !hook.contains("Otherwise upgrade: curl -fsSL"),
        "SessionStart hook should not print raw upgrade commands when /init owns repair"
    );
    assert!(
        hook.contains("for i in 1 2 3") && hook.contains("curl -fsS -m 3"),
        "SessionStart hook should retry daemon health checks to avoid false down reports"
    );
}

#[test]
fn pages_skill_replaces_read() {
    // `/read` was renamed to `/pages` (browse + preview). The pages skill
    // must exist, the read skill must be gone, and no user-facing doc may
    // still advertise `/read`.
    let pages_path = repo_root().join("plugin/skills/pages/SKILL.md");
    assert!(
        pages_path.is_file(),
        "missing pages skill: {}",
        pages_path.display()
    );
    let read_path = repo_root().join("plugin/skills/read/SKILL.md");
    assert!(
        !read_path.exists(),
        "read skill should be deleted (renamed to /pages): {}",
        read_path.display()
    );

    for doc in [
        "plugin/skills/help/SKILL.md",
        "plugin/skills/README.md",
        "plugin/.claude-plugin/README.md",
    ] {
        let text = read_text(doc);
        assert!(
            !text.contains("/read"),
            "{doc} still advertises the removed /read command"
        );
        assert!(
            text.contains("/pages"),
            "{doc} should advertise the /pages command"
        );
    }
}