oxios_kernel/skill/
prompt.rs1#![allow(missing_docs)]
2use super::types::SkillEntry;
5use std::path::Path;
6
7pub fn escape_xml(s: &str) -> String {
8 s.replace('&', "&")
9 .replace('<', "<")
10 .replace('>', ">")
11 .replace('"', """)
12 .replace('\'', "'")
13}
14
15pub fn compact_path(path: &Path) -> String {
16 if let Some(home) = dirs::home_dir() {
17 let home_str = home.to_string_lossy();
18 let path_str = path.to_string_lossy();
19 if let Some(rest) = path_str.strip_prefix(home_str.as_ref()) {
20 return format!("~{rest}");
21 }
22 }
23 path.to_string_lossy().into_owned()
24}
25
26pub fn format_skills_for_prompt(skills: &[&SkillEntry]) -> String {
27 if skills.is_empty() {
28 return String::new();
29 }
30 let mut lines = vec![
31 "\n\nThe following skills provide specialized instructions for specific tasks.".into(),
32 "Use the read tool to load a skill's file when the task matches its description.".into(),
33 "When a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.".into(),
34 String::new(),
35 "<available_skills>".into(),
36 ];
37 for skill in skills {
38 lines.push(" <skill>".into());
39 lines.push(format!(
40 " <name>{}</name>",
41 escape_xml(&skill.skill.name)
42 ));
43 lines.push(format!(
44 " <description>{}</description>",
45 escape_xml(&skill.skill.description)
46 ));
47 lines.push(format!(
48 " <location>{}</location>",
49 escape_xml(&compact_path(&skill.skill.file_path))
50 ));
51 lines.push(" </skill>".into());
52 }
53 lines.push("</available_skills>".into());
54 lines.join("\n")
55}