-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrender.rs
More file actions
111 lines (94 loc) · 5.42 KB
/
Copy pathrender.rs
File metadata and controls
111 lines (94 loc) · 5.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
use crate::skills::model::SkillMetadata;
pub fn render_skills_section(skills: &[SkillMetadata]) -> Option<String> {
let skills: Vec<&SkillMetadata> = skills
.iter()
.filter(|skill| skill.allow_implicit_invocation())
.collect();
if skills.is_empty() {
return None;
}
let mut lines: Vec<String> = Vec::new();
lines.push("## Skills".to_string());
lines.push("A skill is a set of local instructions to follow that is stored in a `SKILL.md` file. Below is the list of skills that can be used. Each entry includes a name, description, and file path so you can open the source for full instructions when using a specific skill.".to_string());
lines.push("### Available skills".to_string());
for skill in skills {
let path_str = skill.path.to_string_lossy().replace('\\', "/");
let name = skill.name.as_str();
let description = skill.description.as_str();
lines.push(format!("- {name}: {description} (file: {path_str})"));
}
lines.push("### How to use skills".to_string());
lines.push(
r###"- Discovery: The list above is the skills available in this session (name + description + file path). Skill bodies live on disk at the listed paths.
- Trigger rules: If the user names a skill (with `$SkillName` or plain text) OR the task clearly matches a skill's description shown above, you must use that skill for that turn. Multiple mentions mean use them all. Do not carry skills across turns unless re-mentioned.
- Missing/blocked: If a named skill isn't in the list or the path can't be read, say so briefly and continue with the best fallback.
- How to use a skill (progressive disclosure):
1) After deciding to use a skill, open its `SKILL.md`. Read only enough to follow the workflow.
2) When `SKILL.md` references bundled skill resources or scripts with relative paths such as `scripts/foo.py`, resolve them relative to the directory containing that `SKILL.md` first, and only consider other paths if needed.
3) If `SKILL.md` points to extra folders such as `references/`, load only the specific files needed for the request; don't bulk-load everything.
4) If `scripts/` exist, prefer running or patching them instead of retyping large code blocks.
5) If `assets/` or templates exist, reuse them instead of recreating from scratch.
- Coordination and sequencing:
- If multiple skills apply, choose the minimal set that covers the request and state the order you'll use them.
- Announce which skill(s) you're using and why (one short line). If you skip an obvious skill, say why.
- Context hygiene:
- Keep context small: summarize long sections instead of pasting them; only load extra files when needed.
- Avoid deep reference-chasing: prefer opening only files directly linked from `SKILL.md` unless you're blocked.
- When variants exist (frameworks, providers, domains), pick only the relevant reference file(s) and note that choice.
- Safety and fallback: If a skill can't be applied cleanly (missing files, unclear instructions), state the issue, pick the next-best approach, and continue."###
.to_string(),
);
Some(lines.join("\n"))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::skills::model::SkillPolicy;
use crate::skills::model::SkillScope;
use std::path::PathBuf;
fn skill(name: &str, allow_implicit_invocation: Option<bool>) -> SkillMetadata {
SkillMetadata {
name: name.to_string(),
description: format!("{name} description"),
short_description: None,
path: PathBuf::from(format!("/tmp/{name}/SKILL.md")),
scope: SkillScope::User,
content: String::new(),
policy: allow_implicit_invocation.map(|allow_implicit_invocation| SkillPolicy {
allow_implicit_invocation: Some(allow_implicit_invocation),
}),
}
}
#[test]
fn render_skills_section_omits_manual_only_skills() {
let rendered = render_skills_section(&[
skill("implicit", None),
skill("manual", Some(false)),
])
.expect("implicit skill should render");
assert!(rendered.contains("- implicit: implicit description"));
assert!(!rendered.contains("- manual: manual description"));
}
#[test]
fn render_skills_section_returns_none_for_only_manual_skills() {
let rendered = render_skills_section(&[skill("manual", Some(false))]);
assert!(rendered.is_none());
}
#[test]
fn render_skills_section_uses_full_description_for_model_context() {
let mut skill = skill("compact", None);
skill.description = "full trigger description".to_string();
skill.short_description = Some("compact UI summary".to_string());
let rendered = render_skills_section(&[skill]).expect("skill should render");
assert!(rendered.contains("- compact: full trigger description"));
assert!(!rendered.contains("compact UI summary"));
}
#[test]
fn render_skills_section_resolves_relative_paths_from_skill_dir() {
let rendered = render_skills_section(&[skill("helper", None)])
.expect("implicit skill should render");
assert!(rendered.contains(
"When `SKILL.md` references bundled skill resources or scripts with relative paths such as `scripts/foo.py`, resolve them relative to the directory containing that `SKILL.md` first, and only consider other paths if needed."
));
}
}