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