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    /// Media skills (video-analyze, scene-explain, vlc-control, watch-together):
43    /// they drive the runtime's media tools rather than the four-stage pipeline.
44    Media,
45}
46
47/// Where a skill came from. Drives the curation gate: `Llm`-authored skills
48/// cannot auto-promote past `Emerging` until a human curates them
49/// (amendment A1, `2026-05-28-mur-workflow-engine-design-v2.md`).
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
51#[serde(rename_all = "lowercase")]
52pub enum Provenance {
53    /// Hand-authored by a person. Default — no gate.
54    #[default]
55    Human,
56    /// Produced by the LLM extraction judge. Gated until curated.
57    Llm,
58    /// LLM-extracted, then human-reviewed/edited. No gate.
59    Hybrid,
60}
61
62/// Exactly one content mode is populated; see spec §3.2.3.
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
64#[serde(rename_all = "lowercase")]
65pub enum ContentMode {
66    Context,
67    Workflow,
68    Command,
69    Note,
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
73#[serde(rename_all = "lowercase")]
74pub enum Priority {
75    Low,
76    #[default]
77    Normal,
78    High,
79    Critical,
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Ord, PartialOrd)]
83#[serde(rename_all = "snake_case")]
84pub enum TriggerKind {
85    Command,
86    Keyword,
87    SessionStart,
88    Manual,
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn host_id_serialises_kebab_case() {
97        let yaml = serde_yaml_ng::to_string(&HostId::MurAgent).unwrap();
98        assert_eq!(yaml.trim(), "mur-agent");
99    }
100
101    #[test]
102    fn trust_level_ordering_matches_spec() {
103        assert!(TrustLevel::Sandboxed < TrustLevel::Verified);
104        assert!(TrustLevel::Verified < TrustLevel::Trusted);
105    }
106
107    #[test]
108    fn host_id_default_is_all() {
109        assert_eq!(HostId::default(), HostId::All);
110    }
111
112    #[test]
113    fn note_category_serialises_lowercase_and_roundtrips() {
114        let yaml = serde_yaml_ng::to_string(&Category::Note).unwrap();
115        assert_eq!(yaml.trim(), "note");
116        let parsed: Category = serde_yaml_ng::from_str("note").unwrap();
117        assert_eq!(parsed, Category::Note);
118    }
119
120    #[test]
121    fn media_category_serialises_lowercase_and_roundtrips() {
122        let yaml = serde_yaml_ng::to_string(&Category::Media).unwrap();
123        assert_eq!(yaml.trim(), "media");
124        let parsed: Category = serde_yaml_ng::from_str("media").unwrap();
125        assert_eq!(parsed, Category::Media);
126    }
127
128    #[test]
129    fn note_content_mode_serialises_lowercase_and_roundtrips() {
130        let yaml = serde_yaml_ng::to_string(&ContentMode::Note).unwrap();
131        assert_eq!(yaml.trim(), "note");
132        let parsed: ContentMode = serde_yaml_ng::from_str("note").unwrap();
133        assert_eq!(parsed, ContentMode::Note);
134    }
135
136    #[test]
137    fn provenance_defaults_to_human_and_roundtrips() {
138        // Default is Human (a skill is human-authored unless stated otherwise).
139        assert_eq!(Provenance::default(), Provenance::Human);
140        // Serializes lowercase, like Category.
141        let yaml = serde_yaml_ng::to_string(&Provenance::Llm).unwrap();
142        assert_eq!(yaml.trim(), "llm");
143        let parsed: Provenance = serde_yaml_ng::from_str("hybrid").unwrap();
144        assert_eq!(parsed, Provenance::Hybrid);
145    }
146}