Help us shape the future of Go! Take the 2026 Go Developer Survey

skill

package
v0.35.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 31, 2026 License: MIT Imports: 12 Imported by: 18

Documentation

Overview

Package skill loads and parses Agent Skills ("SKILL.md") files — the unified loader shared by exegesis and skillsaw. A skill is a directory containing a SKILL.md in the Anthropic Agent Skills format: YAML frontmatter (name and description, optionally other keys) delimited by "---" lines, followed by a Markdown body. Parsing is pure once the bytes are read; only Load and the Discover functions touch the filesystem.

Index

Constants

View Source
const (
	MetadataKey = "metadata"
	LineageKey  = "lineage"
)

Frontmatter keys the lineage declaration lives under.

**Nested under `metadata`, not top-level**, because speclint's allowlist is the agentskills.io published set and its own rule is that anything outside the spec belongs under `metadata`. A top-level `lineage:` would be a second deliberate deviation beside `tags`, and the value of "one deviation, for the key 163 corpora already use" is that there is one.

View Source
const FileName = "SKILL.md"

FileName is the required filename inside a skill directory.

View Source
const LineagePath = MetadataKey + "." + LineageKey

LineagePath is the dotted path a diagnostic names, so a reader is sent to the right line rather than to a top-level key that would be rejected.

Variables

This section is empty.

Functions

func DefaultRoots

func DefaultRoots() []string

DefaultRoots returns the runtime-neutral directories a multi-root discovery scans. The darwin source hard-codes .claude/skills, but runtime neutrality requires covering every skills-compatible runtime.

func Discover

func Discover(tree string) ([]string, error)

Discover returns every immediate subdirectory of tree that contains a SKILL.md, sorted by name. It errors if tree cannot be read. This is the single-tree walk the exegesis gates iterate over.

func DiscoverRoots

func DiscoverRoots(base string, roots []string) ([]string, error)

DiscoverRoots returns every skill directory under any of roots (relative to base), skipping roots absent in this environment and deduplicating. This is the multi-runtime scan skillsaw's "--all" performs.

func Slug

func Slug(s string) string

Slug normalizes a string to the Agent Skills slug form: lowercase, every run of non-[a-z0-9] becomes one hyphen, leading and trailing hyphens are trimmed, and the result is capped at 64 runes; an empty result becomes "skill". It is idempotent: Slug(Slug(x)) == Slug(x).

It does not NFKD-fold accents (é stays a separator, not "e"), avoiding an x/text dependency; skill names are ASCII kebab-case in practice.

Types

type Lineage added in v0.23.0

type Lineage string

Lineage is how a skill's content came to exist: distilled from a source text, or authored directly.

**It is declared, never derived**, and the distinction is the whole point. Content cannot tell you a skill's lineage: a hand-written skill has no RIA segments because it never claimed the format, and a book-derived one that has lost its segments is malformed — the two look identical from the outside and want opposite treatment. So a predicate over the current shape would answer the wrong question, however carefully it were tuned.

This is the opposite epistemics from the executes-anything predicate skillsaw and adh use to scope their boundary checks, and the resemblance is a trap. That one *must* be derived, because nobody declares whether an artifact runs commands. This one *must not* be, because the document is the only witness to its own origin. A shared mechanism would force one of the two to be wrong.

Closed and typed, unlike finding.Category, which stays a bare string because its drift is cosmetic and nothing branches on it. A lineage typo changes **which checks run**, so where the kernel owns the rules a value selects, it owns the value's vocabulary.

const (
	// LineageUnset is the zero value: the skill declares nothing.
	//
	// **It is treated as strictly as a declared book lineage, and that is deliberate.**
	// The tempting reading is "undeclared means hand-written, so skip the format
	// checks", and it is the reading to refuse: it hands every skill an escape from the
	// RIA-TV++ contract by omission, which is the same hole as a book skill shedding
	// headings until it no longer looks like one. A contract you leave by saying
	// nothing is not a contract.
	//
	// The cost is honest and bounded: a corpus predating this field keeps its
	// diagnostics until each hand-written skill adds one line. That is a migration
	// rather than a false positive, which is the difference this field buys.
	LineageUnset Lineage = ""
	// BookDerived is distilled from a source text, so the RIA-TV++ segments are the
	// contract it was produced against.
	BookDerived Lineage = "book-derived"
	// HandWritten is authored directly. It never claimed the RIA-TV++ format, so
	// grading it against those segments reports a defect in a format the document does
	// not use.
	HandWritten Lineage = "hand-written"
)

Lineage values, closed. A skill declares one; nothing infers it.

func ParseLineage added in v0.23.0

func ParseLineage(s string) (Lineage, bool)

ParseLineage reads a declared lineage, reporting whether the value is recognised.

**An unrecognised value is not silently accepted and not silently rejected.** ok is false and the returned Lineage is LineageUnset, so a caller gets the strictest treatment *and* can report the value it did not understand. That pairing is the point: `hand-written` must not quietly buy lenient treatment, and it must not fail closed without saying why either.

There are no aliases. A typo and a newer vocabulary want the same handling, and an alias table manufactures synonyms that never shrink; vocabulary evolution belongs to a format version.

func (Lineage) ClaimsRIASegments added in v0.23.0

func (l Lineage) ClaimsRIASegments() bool

ClaimsRIASegments reports whether a skill of this lineage was produced against the RIA-TV++ segment contract, and so should be graded on it.

Only a declared hand-written lineage is exempt. Unset and unrecognised are both graded, which is what stops the exemption from being reachable by omission or by typo.

type Skill

type Skill struct {
	Dir             string   // skill directory
	Path            string   // <Dir>/SKILL.md
	Name            string   // frontmatter name
	Description     string   // frontmatter description
	FrontmatterKeys []string // top-level frontmatter keys, sorted (for lint)
	Frontmatter     string   // raw YAML frontmatter block (between the --- lines)
	Body            string   // markdown body after the frontmatter

	// Lineage is the recognised `lineage:` declaration, LineageUnset when absent *or*
	// unrecognised, so a caller that ignores LineageRaw gets the strictest treatment.
	//
	// LineageRaw is the value as written, kept so an unrecognised one can be reported
	// verbatim rather than described. Unrecognised is therefore
	// `LineageRaw != "" && Lineage == LineageUnset` — derived rather than a second
	// boolean, because a bool's zero value would say "unrecognised" on the
	// FrontmatterErr path, where nothing was parsed and there is nothing to report.
	// That is the trap the note on FrontmatterErr describes, one field over.
	//
	// A bad value is not a load failure, for the same reason FrontmatterErr is not: one
	// malformed skill must not stop a caller walking a tree.
	Lineage    Lineage
	LineageRaw string
	Raw        string // full file contents
	Bytes      int    // byte size of Raw

	// FrontmatterErr is the error from parsing the YAML frontmatter, or nil when it
	// parsed. When it is non-nil, Name, Description and FrontmatterKeys are zero
	// because nothing could be read out of the block — they are not evidence that
	// those fields are absent from the file. A linter that reports "description is
	// empty" without checking this sends the reader to the wrong line.
	//
	// The underlying YAML error is kept verbatim: it locates the offending line and
	// column, which is the part a reader needs, and nothing classifies on it.
	FrontmatterErr error
}

Skill is a parsed SKILL.md and its location.

func Load

func Load(dir string) (*Skill, error)

Load reads and parses <dir>/SKILL.md. A missing SKILL.md is translated at this boundary to an errs.Error with code ENOTFOUND (classify it via errs.ErrorCode); the external os.ErrNotExist is not propagated. Any other read error is wrapped with Op "skill.Load".

func (*Skill) Hash

func (s *Skill) Hash() string

Hash is the content identity of the skill: identity.Hash of its raw bytes. It is byte-identical to the hash exegesis and skillsaw pin manifests with.

func (*Skill) UnrecognisedLineage added in v0.23.0

func (s *Skill) UnrecognisedLineage() (string, bool)

UnrecognisedLineage reports whether a skill declared a lineage that is not in the vocabulary, and returns it verbatim so a diagnostic can quote what was written.

A caller that never asks still behaves safely -- Lineage is LineageUnset in that case, which is graded strictly. What asking adds is the report, and the report is what stops a typo from being indistinguishable from silence.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL