Skip to main content

vtcode_skills/
instructions.rs

1use std::path::PathBuf;
2
3/// Prefix for skill instructions (used to detect skill instruction messages).
4pub const SKILL_INSTRUCTIONS_PREFIX: &str = "<skill>";
5
6#[derive(Debug, Clone)]
7pub struct SkillInstructions {
8    pub(crate) name: String,
9    pub(crate) path: PathBuf,
10    pub(crate) contents: String,
11}
12
13impl SkillInstructions {
14    /// Check if a message contains skill instructions.
15    fn is_skill_instructions(text: &str) -> bool {
16        text.starts_with(SKILL_INSTRUCTIONS_PREFIX)
17    }
18
19    /// Format skill instructions as an XML-wrapped message (Codex-compatible format).
20    fn to_xml_message(&self) -> String {
21        let path_str = self.path.to_string_lossy().replace('\\', "/");
22        format!("<skill>\n<name>{}</name>\n<path>{}</path>\n{}\n</skill>", self.name, path_str, self.contents)
23    }
24}
25
26#[cfg(test)]
27mod tests {
28    use super::*;
29
30    #[test]
31    fn test_skill_instructions_prefix_detection() {
32        let msg = "<skill>\n<name>test</name>\n<path>/path</path>\ncontents\n</skill>";
33        assert!(SkillInstructions::is_skill_instructions(msg));
34
35        let non_skill = "Regular message without skill";
36        assert!(!SkillInstructions::is_skill_instructions(non_skill));
37    }
38
39    #[test]
40    fn test_skill_instructions_xml_format() {
41        let skill = SkillInstructions {
42            name: "test-skill".to_string(),
43            path: PathBuf::from("/path/to/skill/SKILL.md"),
44            contents: "# Test Skill\n\nInstructions here.".to_string(),
45        };
46
47        let xml = skill.to_xml_message();
48        assert!(xml.starts_with("<skill>"));
49        assert!(xml.contains("<name>test-skill</name>"));
50        assert!(xml.contains("<path>/path/to/skill/SKILL.md</path>"));
51        assert!(xml.contains("# Test Skill"));
52        assert!(xml.ends_with("</skill>"));
53    }
54}