zeroclaw_runtime/skills/
frontmatter.rs1use serde::{Deserialize, Serialize};
17use zeroclaw_config::traits::{PropFieldInfo, PropKind};
18
19#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
20pub struct SkillFrontmatter {
21 pub name: String,
22 pub description: String,
23 #[serde(default, skip_serializing_if = "Option::is_none")]
24 pub license: Option<String>,
25 #[serde(default, skip_serializing_if = "Option::is_none")]
26 pub author: Option<String>,
27 #[serde(default, skip_serializing_if = "Option::is_none")]
28 pub version: Option<String>,
29 #[serde(default, skip_serializing_if = "Option::is_none")]
30 pub category: Option<String>,
31}
32
33impl SkillFrontmatter {
34 pub fn prop_fields() -> Vec<PropFieldInfo> {
37 vec![
38 field(
39 "name",
40 "String",
41 true,
42 "Skill identifier (lowercase, hyphens only).",
43 ),
44 field(
45 "description",
46 "String",
47 true,
48 "What the skill does and when to use it. Written in third person; injected into the system prompt for skill discovery.",
49 ),
50 field(
51 "license",
52 "Option<String>",
53 false,
54 "SPDX license identifier (e.g. MIT).",
55 ),
56 field(
57 "author",
58 "Option<String>",
59 false,
60 "Skill author handle or organisation.",
61 ),
62 field(
63 "version",
64 "Option<String>",
65 false,
66 "SemVer version of the skill. Defaults to 0.1.0 on scaffold.",
67 ),
68 field(
69 "category",
70 "Option<String>",
71 false,
72 "Skill category for registry grouping (e.g. coding, ops).",
73 ),
74 ]
75 }
76}
77
78fn field(
79 name: &'static str,
80 type_hint: &'static str,
81 required: bool,
82 description: &'static str,
83) -> PropFieldInfo {
84 PropFieldInfo {
85 name: name.to_string(),
86 category: "skill-frontmatter",
87 display_value: if required {
88 String::from("<required>")
89 } else {
90 String::new()
91 },
92 type_hint,
93 kind: PropKind::String,
94 is_secret: false,
95 enum_variants: None,
96 description,
97 derived_from_secret: false,
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn prop_fields_matches_struct() {
107 let fields = SkillFrontmatter::prop_fields();
110 assert_eq!(
111 fields.len(),
112 6,
113 "SkillFrontmatter::prop_fields drifted from struct definition; \
114 update both when adding/removing fields"
115 );
116 }
117
118 #[test]
119 fn serializes_minimal_skill_without_optional_fields() {
120 let fm = SkillFrontmatter {
121 name: "code-review".into(),
122 description: "Review pull requests.".into(),
123 ..Default::default()
124 };
125 let json = serde_json::to_value(&fm).unwrap();
126 assert_eq!(json["name"], "code-review");
127 assert_eq!(json["description"], "Review pull requests.");
128 assert!(json.get("license").is_none());
129 assert!(json.get("author").is_none());
130 }
131}