v.skills #
Module skills owns the bundled agent skills that ship with the V compiler and the rules for installing them into a project or a user-wide directory.
A skill is a directory under vlib/v/skills/<name>/ holding a SKILL.md with YAML front matter (name, description) plus optional extra files in references/ and scripts/. The bundles are read from the V source tree at run time rather than embedded into a binary, so a skill can be reviewed, edited and diffed in the repository, and adding one needs no rebuild.
Both v skills (cmd/tools/vskills) and the v_skills MCP tool (cmd/tools/vmcp) go through this module, so the catalog, the layout and the install rules cannot drift apart.
Constants #
const entry_file = 'SKILL.md'
entry_file is the file every skill directory must contain. It is both the entry point an agent reads first and the marker catalog looks for, so a directory without one is never offered as a skill.
const origin_file = 'origin.json'
origin_file records what a bundle looked like when it was installed, so a later bundle change can be told apart from a local edit.
It sits in the install directory's own root rather than inside each skill, because list_files walks a skill directory and would otherwise count it as a skill file.
const max_name_length = 64
max_name_length is the longest a skill name may be, per the Agent Skills spec.
const max_description_length = 1024
max_description_length is the longest a skill description may be. The description is the only part an agent sees before it decides to load the skill, so the spec puts a ceiling on it; a longer one would be truncated anyway.
const project_dir = '.agents/skills'
project_dir is the per-project install location, relative to a project root. It matches the layout opencode, Claude Code and friends already scan.
const global_dir = '.agents/skills'
global_dir is the install location relative to the user's home directory, so a skill installed with --global applies to every project.
fn bundled_root #
fn bundled_root(vroot string) string
bundled_root returns <vroot>/vlib/v/skills.
fn catalog #
fn catalog(vroot string) []Skill
catalog returns every skill bundled with the compiler, sorted by name.
A directory qualifies when it contains SKILL.md; a directory without one is skipped rather than reported, so an in-progress bundle cannot break v skills list.
fn content_digest #
fn content_digest(directory string, files []string) !string
content_digest is one value describing the files files names in directory.
The digest covers the file names as well as their bytes, so a rename changes it, and the files are sorted so the order they were listed in does not. An unreadable file returns an error; an incomplete digest cannot prove content is unchanged.
fn find #
fn find(vroot string, name string) ?Skill
find returns the bundled skill called name, or none for an invalid name.
fn forget_origin #
fn forget_origin(dir string, name string)
forget_origin drops the record for name, so a reinstall is treated as new.
Removing a skill has to do this: a record left behind describes a digest for files that no longer exist, and a later reinstall of the same name would be compared against it and read as modified.
fn human_size #
fn human_size(bytes int) string
human_size renders a byte count compactly, for file listings.
fn install #
fn install(skill Skill, dir string, opts InstallOptions) !InstallResult
install copies skill into dir.
The bundle is validated first, so a skill whose front matter does not satisfy the spec is refused here rather than copied somewhere an agent will read it. Its name must match the bundle and its destination must be an immediate child of the install directory. File paths must stay within that skill and its bundle and refer to regular files.
Without force, an already installed skill is skipped and reported as such rather than overwritten: an agent must not silently discard local edits to a checked-in skill. dry_run computes the same result without writing.
A symlink sitting where the skill would go is refused. os.is_dir and os.rmdir_all both follow their argument, so --force over a link would list and delete the target's contents rather than the link. Provenance symlinks and non-regular files are refused before installed content is replaced.
fn installed #
fn installed(dir string) []string
installed returns the names installed in dir, sorted.
fn invalid_bundled #
fn invalid_bundled(vroot string) []string
invalid_bundled returns the bundled skills whose front matter does not satisfy the Agent Skills spec, as "name: reason" lines.
catalog skips what it cannot read, which is right for a listing but would leave a malformed bundle invisible. v skills list reports these instead, so a broken bundle shows up before someone tries to install it.
fn list_files #
fn list_files(directory string) []string
list_files returns every file below directory as paths relative to it, with SKILL.md first. The rest are sorted so a reinstall reports a stable order.
fn origin_state #
fn origin_state(vroot string, dir string, name string) OriginState
origin_state reports how the skill name installed in dir relates to the bundle of the same name under vroot.
A skill that is no longer bundled is current: there is nothing to update it to, so it is left to whoever installed it.
fn out_of_date #
fn out_of_date(vroot string, dir string) []string
out_of_date returns the bundled skills installed in dir whose installed copy no longer matches the bundled one. This content comparison cannot distinguish local edits from an unchanged installation of an older bundle. Use refresh_candidates when deciding which installations can be safely updated.
fn parse_front_matter #
fn parse_front_matter(content string) ?map[string]string
parse_front_matter reads the name: and description: keys from a SKILL.md YAML front matter block.
The parser is deliberately narrow: it accepts the leading --- block, then reads the flat key: value keys a skill needs. A missing block or a missing key yields none rather than a guessed value, so v skills add never installs a bundle whose metadata it could not read.
fn refresh_candidates #
fn refresh_candidates(vroot string, dir string) ([]string, []string)
refresh_candidates returns the skills installed in dir that no longer match their bundle, split by whether refreshing them is safe.
refreshable holds the stale ones: their files are still the ones that were installed, so copying the newer bundle over them cannot lose anything. held_back holds the modified and unknown ones, where refreshing would discard a local edit, or where nothing records what was installed. Both are the caller's to decide about, which is why they are not returned as one list.
A skill that is current appears in neither.
fn relative_to #
fn relative_to(base string, path string) string
relative_to renders path relative to base with forward slashes, for readable tool output. The base itself reads as ., and a path outside base is returned unchanged.
fn remove #
fn remove(dir string, name string) !RemoveResult
remove deletes an installed skill directory. It reports removed: false when nothing was installed under that name. Names follow validate_name; symlinks and targets outside the immediate install directory are refused.
fn strip_bom #
fn strip_bom(content string) string
strip_bom removes a leading byte order mark from content.
fn target_dir #
fn target_dir(scope Scope, base string) string
target_dir returns the directory a skill installs into for scope. base is the project root for Scope.project_root and is ignored for Scope.home_dir.
fn validate_bundle #
fn validate_bundle(directory string) !string
validate_bundle checks one skill directory against the Agent Skills spec and returns its name, or an error naming the first rule it breaks.
The name is what an agent matches a task against and the directory is what v skills remove addresses, so the spec requires the two to agree; a bundle where they disagree is one an agent will load under a name the installer cannot find again.
Installing checks this too, so a bundle that fails is refused rather than copied somewhere an agent will read it.
fn validate_name #
fn validate_name(name string) !string
validate_name checks one skill name and returns it, or an error naming the rule it breaks.
fn OriginState.from #
fn OriginState.from[W](input W) !OriginState
from returns the OriginState value of an integer or of a field name, or an error if there is none.
fn Scope.from #
fn Scope.from[W](input W) !Scope
from returns the Scope value of an integer or of a field name, or an error if there is none.
enum OriginState #
enum OriginState {
// current means the installed files are exactly what was installed and the
// bundle still matches it.
current
// stale means the installed files are still what was installed, but the
// bundle has since changed. Refreshing cannot lose anything.
stale
// modified means the installed files no longer match what was installed, so
// they were edited here. Refreshing would discard that, and `update` will not
// do it without `force`.
modified
// unknown means there is no usable proof of unchanged content: unreadable files, an installation
// from before `origin_file` existed, or one written by hand. Treated as
// modified, because guessing wrong loses the user's files and guessing the
// other way only asks for `--force`.
unknown
}
OriginState is what an installed skill looks like relative to its bundle.
The distinction matters because the two wrong answers are not the same event: a bundle that moved on is safe to refresh, while a local edit is the user's work. Content comparison alone cannot tell them apart, so origin_file records the digests that were installed and this reports which case applies.
fn (OriginState) str #
fn (s OriginState) str() string
str renders a state as the word v skills list and v skills update report.
enum Scope #
enum Scope {
// project_root installs under `<project root>/.agents/skills`, which is
// committed with the repository and shared with the whole team.
project_root
// home_dir installs under `<home>/.agents/skills`, which applies to every
// project on the machine for the current user.
home_dir
}
Scope selects where a skill is installed.
struct InstallOptions #
struct InstallOptions {
pub:
// force overwrites an already installed skill of the same name.
force bool
// dry_run reports what would happen without touching the filesystem.
dry_run bool
}
InstallOptions is what install needs beyond the skill and the target.
struct InstallResult #
struct InstallResult {
pub:
skill string
// path is the installed `SKILL.md`.
path string
// skipped is true when the skill was already installed and `force` was off.
skipped bool
// dry_run is true when nothing was written.
dry_run bool
pub mut:
// written lists the files copied, relative to the skill directory.
written []string
}
InstallResult reports what one install call did.
struct RemoveResult #
struct RemoveResult {
pub:
skill string
// path is the removed directory.
path string
removed bool
}
RemoveResult reports what one remove call did.
struct Skill #
struct Skill {
pub:
name string
description string
// directory is the path of the bundled skill directory.
directory string
// files are the skill's files relative to `directory`, `SKILL.md` first.
files []string
}
Skill describes one bundled skill directory.
- README
- Constants
- fn bundled_root
- fn catalog
- fn content_digest
- fn find
- fn forget_origin
- fn human_size
- fn install
- fn installed
- fn invalid_bundled
- fn list_files
- fn origin_state
- fn out_of_date
- fn parse_front_matter
- fn refresh_candidates
- fn relative_to
- fn remove
- fn strip_bom
- fn target_dir
- fn validate_bundle
- fn validate_name
- fn OriginState.from
- fn Scope.from
- enum OriginState
- enum Scope
- struct InstallOptions
- struct InstallResult
- struct RemoveResult
- struct Skill