Skip to main content

agentzero_tools/skills/
skillforge.rs

1use 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}