agentzero_tools/skills/
skillforge.rs1use anyhow::bail;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub struct SkillTemplate {
5 pub name: String,
6 pub description: String,
7}
8
9pub fn validate_skill_name(name: &str) -> anyhow::Result<()> {
10 if name.trim().is_empty() {
11 bail!("skill name cannot be empty");
12 }
13 if !name
14 .chars()
15 .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
16 {
17 bail!("skill name contains invalid characters");
18 }
19 Ok(())
20}
21
22pub fn render_skill_markdown(template: &SkillTemplate) -> anyhow::Result<String> {
23 validate_skill_name(&template.name)?;
24 if template.description.trim().is_empty() {
25 bail!("skill description cannot be empty");
26 }
27
28 Ok(format!(
29 "# {}\n\n## Purpose\n{}\n\n## Usage\n- Add clear instructions and guardrails for this skill.\n",
30 template.name, template.description
31 ))
32}
33
34#[cfg(test)]
35mod tests {
36 use super::{render_skill_markdown, SkillTemplate};
37
38 #[test]
39 fn render_skill_markdown_success_path() {
40 let markdown = render_skill_markdown(&SkillTemplate {
41 name: "my_skill".to_string(),
42 description: "Test skill".to_string(),
43 })
44 .expect("render should succeed");
45
46 assert!(markdown.contains("# my_skill"));
47 assert!(markdown.contains("## Purpose"));
48 }
49
50 #[test]
51 fn render_skill_markdown_rejects_invalid_name_negative_path() {
52 let err = render_skill_markdown(&SkillTemplate {
53 name: "bad skill".to_string(),
54 description: "Test skill".to_string(),
55 })
56 .expect_err("invalid name should fail");
57
58 assert!(err.to_string().contains("invalid characters"));
59 }
60}