Skip to main content

vtcode_core/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 name: String,
9    pub path: PathBuf,
10    pub contents: String,
11}
12
13impl SkillInstructions {
14    /// Check if a message contains skill instructions.
15    pub 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    pub fn to_xml_message(&self) -> String {
21        let path_str = self.path.to_string_lossy().replace('\\', "/");
22        format!(
23            "<skill>\n<name>{}</name>\n<path>{}</path>\n{}\n</skill>",
24            self.name, path_str, self.contents
25        )
26    }
27}
28
29#[cfg(test)]
30mod tests {
31    use super::*;
32
33    #[test]
34    fn test_skill_instructions_prefix_detection() {
35        let msg = "<skill>\n<name>test</name>\n<path>/path</path>\ncontents\n</skill>";
36        assert!(SkillInstructions::is_skill_instructions(msg));
37
38        let non_skill = "Regular message without skill";
39        assert!(!SkillInstructions::is_skill_instructions(non_skill));
40    }
41
42    #[test]
43    fn test_skill_instructions_xml_format() {
44        let skill = SkillInstructions {
45            name: "test-skill".to_string(),
46            path: PathBuf::from("/path/to/skill/SKILL.md"),
47            contents: "# Test Skill\n\nInstructions here.".to_string(),
48        };
49
50        let xml = skill.to_xml_message();
51        assert!(xml.starts_with("<skill>"));
52        assert!(xml.contains("<name>test-skill</name>"));
53        assert!(xml.contains("<path>/path/to/skill/SKILL.md</path>"));
54        assert!(xml.contains("# Test Skill"));
55        assert!(xml.ends_with("</skill>"));
56    }
57}