skills actions — what each tool does
All four operations share one flow: execute dispatches on the
SkillAction, the skills are
discovered from the configured sources, filters are applied,
and the matching output is assembled. The
Skills wrapper methods (list, read,
read_asset, match_skills) are thin conveniences over
execute(&SkillSchema).
List
Returns a SkillOutput::List with the name + description of every
discovered skill (after filters):
let out = skills.list()?; // SkillOutput::List
match out {
SkillOutput::List { skills } => { /* ... */ }
_ => unreachable!(),
}
- Reads every source, so an unreadable directory source is an error here —
SkillError::InvalidSource("cannot canonicalize path: …"/"not a directory: …"/"cannot read directory: …"). - No skill is ever “not found” in a list — an empty result just means no skills matched the sources and filters.
Read
Returns the full content of one named skill — metadata plus the body with frontmatter stripped:
let out = skills.read("my-skill")?; // SkillOutput::Read
- The name is matched exactly against discovered skill names (after
filters — a skill excluded by
ignore/includeis not readable). - Errors:
SkillError::NotFound("skill not found: my-skill") when no discovered skill has that name. (An internal guard also rejects a call with no name at all asInvalidAction— the wrapper always supplies one.)
Read Asset
Returns the raw content of one asset file inside a skill’s directory:
let out = skills.read_asset("my-skill", "templates/request.json")?;
// SkillOutput::ReadAsset { content }
The asset path is sandboxed to the skill directory:
| Requested path | Result |
|---|---|
| A relative path to an existing file inside the skill dir | ReadAsset { content } |
Absolute path (/etc/passwd) |
SkillError::PathTraversal |
Any .. component (../…) |
SkillError::PathTraversal |
| Resolves (even via symlink) outside the skill dir | SkillError::PathTraversal |
| File does not exist | SkillError::NotFound ("asset not found: …") |
| Skill does not exist | SkillError::NotFound |
| Embedded skill (no directory) | SkillError::InvalidAction ("read_asset is not supported for embedded skills") |
The sandbox works by canonicalizing both the skill directory and the resolved path and checking containment — which is also what catches symlink escapes.
Match
Returns the skills that should be active for the current workspace — those whose glob patterns match at least one of the supplied paths, or that opt out of matching entirely:
let out = skills.match_skills(vec!["src/main.rs".into(), "README.md".into()])?;
// SkillOutput::Match { matched }
A skill is included when any of these holds:
always_apply: truein its frontmatter — matched unconditionally, whatever the paths are.- At least one glob hits — the skill’s
globslist is matched against the paths using the project’s standard glob engine (**works). - No globs and not
always_apply— never matches (a skill must declare something to be auto-activated).
Behavioral notes:
- An empty
match_pathsshort-circuits to an empty result without even discovering skills — there is nothing to match against. - Like
List, the result carries onlySkillInfo(name + description), not bodies —Matchis for deciding what to load, and youreadthe chosen skills afterwards. - Filters (
ignore/include) apply here too: an excluded skill cannot be matched.
Error summary
| Situation | Error |
|---|---|
| Source directory missing / unreadable / not a directory | InvalidSource |
Skill name not found (read / read_asset) |
NotFound |
| Asset file missing | NotFound |
Asset path escapes the skill dir (absolute, .., symlink out) |
PathTraversal |
read_asset on an embedded skill |
InvalidAction |