skill

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Jul 4, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package skill discovers ari's skills and slash commands and renders the lazy, budgeted listing block two carries. A skill is a directory with a SKILL.md; a slash command is a markdown template under commands/. Both share one frontmatter parser, one discovery walk, and one precedence rule, because the moment they drift into two config formats they drift into two sets of bugs (doc 13 section 2.6, D20).

This package reads frontmatter at discovery and bodies only on demand: the whole point of lazy loading is that a repo with fifty skills costs fifty small header reads at session start and nothing in the prompt beyond a name and one line each. Invocation, the step that injects a body into the context, is the next slice; this package stops at Body().

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Budget

func Budget(contextWindow int) int

Budget is the token allowance for the skills listing: one percent of the context window, never above budgetCap. A tiny window still gets a floor of one line's worth so a single skill can always announce itself.

func Discover

func Discover(o Options) ([]Skill, []Warning)

Discover scans the builtin, user, and project layers and returns every loadable skill and command, higher-priority scopes shadowing lower ones by name. Frontmatter is read; bodies are not. A malformed entry becomes a warning, never a failure: one broken skill must not take the session down (doc 13 section 2.3).

func NamedArgs

func NamedArgs(s *Skill, args string) map[string]string

NamedArgs maps a skill's declared arguments onto the whitespace-split argument string, so a slash command like /deploy staging fills $target when the frontmatter declares target as the first argument. Positions past the declared list are left to $ARGUMENTS and the positional $1/$2 forms.

func NearMatches

func NearMatches(names []string, target string) []string

NearMatches returns the discovered names closest to an unknown one, so an invalid invocation returns a helpful validation error rather than a bare miss. It is a cheap prefix-and-substring match, enough to catch a typo.

func RenderList

func RenderList(skills []Skill, budget int, est Estimator) (block string, cut []string)

RenderList builds the block two skills listing: one line per skill, name then description, ranked so the closest scope wins a tie and the whole thing fits the budget. The caller owns the "## Skills" heading, so this returns only the entry lines. Skills that do not fit are dropped and returned in cut, so the caller can note the elision rather than pretend everything listed (doc 13 section 2.5). ModelHidden skills never appear here: they are user-invocable only and must not tempt the model.

est measures the running block so the cut lands on the same token scale as the budget. A nil est falls back to the standard (len+3)/4 estimate.

func Substitute

func Substitute(body, args string, named map[string]string) string

Substitute expands the argument placeholders in a command or skill body. It supports $ARGUMENTS (the whole argument string), $1 $2 ... (positional, whitespace-split), and $name (a declared named argument). A placeholder with no matching argument expands to empty. When the body carries no placeholder at all and arguments were passed, they are appended as a trailing "ARGUMENTS:" line, so a template that forgot to reference them still receives them rather than dropping them silently (doc 13 section 4).

Types

type ArgSpec

type ArgSpec struct {
	Name     string
	Required bool
}

ArgSpec is one declared named argument for $name substitution.

type Estimator

type Estimator func(string) int

Estimator turns a string into a token estimate. The caller passes the same (len+3)/4 estimator the prompt builder uses, so the listing is measured in the same unit as the budget it must fit (doc 07).

type InlineRunner

type InlineRunner func(ctx context.Context, command string) (string, error)

InlineRunner runs one command through the host's permission-gated sh path and returns its output. It is the second inline-shell gate: every command it receives has already matched the skill's allowed-tools, and it submits the command as a normal sh call so the full doc 05 pipeline, safety floor included, still decides (doc 13 section 2.7). A nil runner disables inline execution.

type Invocation

type Invocation struct {
	Message string
	Grants  []string
	Notes   []string
}

Invocation is the result of invoking a skill: the processed body ready to inject as a synthetic user message, the allowed-tools rules to add to the session for the duration of the invocation, and any notes worth logging.

type Kind

type Kind string

Kind separates a directory skill from a command template. They share everything else, so the distinction is one field, not two types.

const (
	KindSkill   Kind = "skill"   // a directory with a SKILL.md
	KindCommand Kind = "command" // a markdown template under commands/
)

type Matcher

type Matcher func(command string) bool

Matcher reports whether a command is permitted by the skill's declared allowed-tools rules, the first inline-shell gate. It carries the doc 05 normalization so a skill cannot smuggle a second command past a narrow allowance. The skill package holds no permission logic of its own, so the host builds this from the parsed rules and passes it in; a nil matcher denies every command, which is the safe default.

type Options

type Options struct {
	// Root is the project root; project skills live under Root/.ari/skills
	// and commands under Root/.ari/commands, walked from Root down to Cwd
	// the way project memory is (D21).
	Root string
	// Cwd is where the project walk ends, so a nested package's skills
	// outrank the root's.
	Cwd string
	// GlobalDir is the user nest; user skills and commands live under it.
	GlobalDir string
	// Builtins is the bundled skill tree, may be nil. A test injects a
	// fstest.MapFS; the binary injects its embedded FS.
	Builtins fs.FS
	// contains filtered or unexported fields
}

Options configures discovery across the three layers.

type Request

type Request struct {
	Args    string            // the whole argument string, for $ARGUMENTS
	Named   map[string]string // resolved named arguments, for $name
	Match   Matcher           // gate one: allowed-tools membership
	Inline  InlineRunner      // gate two: the permission-gated sh path
	Trusted bool              // gates three and four: a human-authored skill in a trusted session
}

Request carries everything an invocation needs from the host: the raw and resolved arguments, the two inline-shell gates, and the session trust flag.

type Scope

type Scope string

Scope is where a skill was discovered, which sets its precedence and, in a later slice, whether inline shell execution is allowed.

const (
	ScopeProject Scope = "project" // .ari/skills or .ari/commands in the repo
	ScopeUser    Scope = "user"    // the global nest
	ScopeBuiltin Scope = "builtin" // bundled with the binary
)

type Skill

type Skill struct {
	Name         string
	Description  string
	Kind         Kind
	Scope        Scope
	Path         string   // the SKILL.md or command .md file
	AllowedTools []string // doc 05 rule strings, active only during invocation
	ArgumentHint string
	Arguments    []ArgSpec
	ModelHidden  bool   // disable-model-invocation: user-invocable only
	Model        string // tier hint, empty for the session default
	Context      string // inline or fresh, empty for the default
	// contains filtered or unexported fields
}

Skill is one discovered skill or command. Everything here is read from the frontmatter at discovery; the body is read only when Body is called, which is what keeps discovery cheap (doc 13 section 2.2).

func (*Skill) Body

func (s *Skill) Body() (string, error)

Body reads and returns the instruction body on demand, the lazy half of lazy loading. It is not read at discovery, so a listed-but-never-invoked skill costs only its frontmatter.

func (*Skill) Invoke

func (s *Skill) Invoke(ctx context.Context, req Request) (Invocation, error)

Invoke loads the skill's body, substitutes its arguments, runs the gated inline-shell pass, and wraps the result in the invocation marker. The allowed-tools rules ride back as Grants so the host can add them to the session; they narrow what the skill's own commands may ask for, never widen what the session allows (doc 13 sections 2.5 and 2.7).

type Warning

type Warning struct {
	Path   string
	Reason string
}

Warning is a skill that failed to load, surfaced to the user and to ari doctor rather than taking the session down: one malformed skill must never break discovery (doc 13 section 2.3).

func (Warning) String

func (w Warning) String() string

Jump to

Keyboard shortcuts

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