Skip to main content

mur_common/skill/
types.rs

1//! Skill type enums.
2
3use serde::{Deserialize, Serialize};
4
5/// Which host(s) may load a skill. See spec §2.3.
6#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
7#[serde(rename_all = "kebab-case")]
8pub enum HostId {
9    MurAgent,
10    MurCommander,
11    /// Default when `hosts:` is omitted — backward compatible.
12    #[default]
13    All,
14    #[serde(untagged)]
15    Custom(String),
16}
17
18/// Three-tier skill trust model. Mirrors mur-commander `trust/level.rs`.
19#[derive(
20    Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord, Default,
21)]
22#[serde(rename_all = "kebab-case")]
23pub enum TrustLevel {
24    /// Peer transfer, agent-generated, untrusted registry.
25    #[default]
26    Sandboxed,
27    /// Registry-verified checksum match, community-reviewed.
28    Verified,
29    /// Built-in, user-promoted, or trusted-publisher-signed.
30    Trusted,
31}
32
33/// Top-level skill category.
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
35#[serde(rename_all = "lowercase")]
36pub enum Category {
37    Context,
38    Workflow,
39    Command,
40    Meta,
41    Note,
42}
43
44/// Where a skill came from. Drives the curation gate: `Llm`-authored skills
45/// cannot auto-promote past `Emerging` until a human curates them
46/// (amendment A1, `2026-05-28-mur-workflow-engine-design-v2.md`).
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
48#[serde(rename_all = "lowercase")]
49pub enum Provenance {
50    /// Hand-authored by a person. Default — no gate.
51    #[default]
52    Human,
53    /// Produced by the LLM extraction judge. Gated until curated.
54    Llm,
55    /// LLM-extracted, then human-reviewed/edited. No gate.
56    Hybrid,
57}
58
59/// Exactly one content mode is populated; see spec §3.2.3.
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
61#[serde(rename_all = "lowercase")]
62pub enum ContentMode {
63    Context,
64    Workflow,
65    Command,
66    Note,
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
70#[serde(rename_all = "lowercase")]
71pub enum Priority {
72    Low,
73    #[default]
74    Normal,
75    High,
76    Critical,
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Ord, PartialOrd)]
80#[serde(rename_all = "snake_case")]
81pub enum TriggerKind {
82    Command,
83    Keyword,
84    SessionStart,
85    Manual,
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn host_id_serialises_kebab_case() {
94        let yaml = serde_yaml_ng::to_string(&HostId::MurAgent).unwrap();
95        assert_eq!(yaml.trim(), "mur-agent");
96    }
97
98    #[test]
99    fn trust_level_ordering_matches_spec() {
100        assert!(TrustLevel::Sandboxed < TrustLevel::Verified);
101        assert!(TrustLevel::Verified < TrustLevel::Trusted);
102    }
103
104    #[test]
105    fn host_id_default_is_all() {
106        assert_eq!(HostId::default(), HostId::All);
107    }
108
109    #[test]
110    fn note_category_serialises_lowercase_and_roundtrips() {
111        let yaml = serde_yaml_ng::to_string(&Category::Note).unwrap();
112        assert_eq!(yaml.trim(), "note");
113        let parsed: Category = serde_yaml_ng::from_str("note").unwrap();
114        assert_eq!(parsed, Category::Note);
115    }
116
117    #[test]
118    fn note_content_mode_serialises_lowercase_and_roundtrips() {
119        let yaml = serde_yaml_ng::to_string(&ContentMode::Note).unwrap();
120        assert_eq!(yaml.trim(), "note");
121        let parsed: ContentMode = serde_yaml_ng::from_str("note").unwrap();
122        assert_eq!(parsed, ContentMode::Note);
123    }
124
125    #[test]
126    fn provenance_defaults_to_human_and_roundtrips() {
127        // Default is Human (a skill is human-authored unless stated otherwise).
128        assert_eq!(Provenance::default(), Provenance::Human);
129        // Serializes lowercase, like Category.
130        let yaml = serde_yaml_ng::to_string(&Provenance::Llm).unwrap();
131        assert_eq!(yaml.trim(), "llm");
132        let parsed: Provenance = serde_yaml_ng::from_str("hybrid").unwrap();
133        assert_eq!(parsed, Provenance::Hybrid);
134    }
135}