skill

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Jul 14, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package skill ships fft's agent skill: the document an AI coding assistant reads before it drives fft on somebody's behalf.

The skill is markdown, compiled into the binary, and `fft skill install` copies it onto disk. Compiled in rather than fetched, because the skill a user installs must be the one that describes the fft they are running — and the drift spec in cmd/fft/skill_drift_test.go resolves every fft invocation in it against the real command tree, so a renamed flag fails the build instead of quietly making the skill wrong. A skill that lies is worse than no skill: an agent without one asks, and an agent with a wrong one acts.

It is documentation, not configuration and not a secret. It is written 0644 under a 0755 directory — unlike everything else fft writes — because the user, their editor and their agent all read it, and 0600 is a credential's mode.

Index

Constants

View Source
const Doc = "SKILL.md"

Doc is the entry point: the file an agent always loads, and the file whose presence in a directory is what identifies that directory as this skill's.

View Source
const Name = "fft"

Name is the skill's name, its directory's name, and the name in SKILL.md's frontmatter. All three have to agree, and a spec asserts that they do.

Variables

View Source
var ErrNotSkill = errors.New("not an fft skill directory")

ErrNotSkill reports a directory that fft did not put there.

It is the whole reason --force is safe to offer. --force means "replace my copy of the skill", and the directory it replaces can come from --dir — from a shell, where a typo is one keystroke. A directory with no SKILL.md in it is somebody else's, and fft will not remove a single file from it.

Functions

func Document

func Document() string

Document is the whole of SKILL.md, frontmatter included: what `fft skill show` prints, and what an agent that is not Claude Code wants appended to its own context file.

func FS

func FS() fs.FS

FS is the skill's tree.

func ProjectDir

func ProjectDir() (string, error)

ProjectDir is ./.claude/skills: the skill for one project, which Claude Code prefers over the personal one when it is working in that directory.

func UserDir

func UserDir() (string, error)

UserDir is ~/.claude/skills: where Claude Code looks for a personal skill.

XDG_CONFIG_HOME is deliberately not consulted, unlike [config.DefaultPath]. That path is fft's to choose; this one is Claude Code's, and honouring XDG here would put the skill somewhere nothing reads it.

Types

type Change

type Change struct {
	// File is the path within the skill: "SKILL.md", "references/recipes.md".
	File string `json:"file" yaml:"file"`

	Status Status `json:"status" yaml:"status"`
}

Change is what installing does to one file. It carries its own tags because it is rendered straight to the user under -o json.

type Meta

type Meta struct {
	Name        string `json:"name" yaml:"name"`
	Description string `json:"description" yaml:"description"`
}

Meta is SKILL.md's YAML frontmatter: the two fields the skill format requires, and the only two an agent reads before deciding whether to open the file at all. A description that does not say when to use the skill is a skill that is never used.

func Parse

func Parse(doc string) (Meta, string, error)

Parse splits SKILL.md into its frontmatter and the body below it.

type Plan

type Plan struct {
	// Dir is the skill's own directory — <root>/fft — absolute, so that what is
	// printed is a path the user can act on.
	Dir string

	Files []Change
}

Plan is what installing into a root directory would do. Building one reads; it writes nothing, so the user can be asked before anything happens to their disk.

func NewPlan

func NewPlan(root string) (Plan, error)

NewPlan works out what installing the skill under root would do.

It refuses a directory that exists and holds no SKILL.md: see ErrNotSkill.

func (Plan) Apply

func (p Plan) Apply() (Plan, error)

Apply writes the skill. By the time it is called, whoever called it has already decided about Plan.Pending — so it overwrites and prunes without asking.

It removes exactly the files the plan named — plus the directories that removing them emptied, which held nothing to consent to. That is the invariant: every file fft deletes appeared in the plan, and every one of them but fft's own litter was shown to the user and agreed to (Plan.Pending is where that distinction is drawn, and why). So an install that reports no changes has made none.

An UNCHANGED file is not rewritten. Re-installing on every run of a script must not churn the mtime of a file an editor or a watcher is holding open.

func (Plan) Pending

func (p Plan) Pending() []Change

Pending are the changes that need the user's blessing: a file of theirs to be overwritten, or one to be removed. Everything else — a new file, a file already correct — needs nobody's permission.

fft's own crash litter is not the user's file, and asking them to consent to the removal of something they have never heard of and did not write teaches them only to say yes without reading. It is still in the plan, and still reported when it goes.

type Status

type Status string

The states a file of the skill can be in, on the way to being installed.

The distinction that earns its keep is UNCHANGED against CONFLICT. Installing twice must be silent and must ask nothing — an agent, or a provisioning script, will do it on every run — while a file the *user* has edited is a decision only they can make. Comparing the bytes is what tells those apart; a timestamp or a version marker would not.

const (
	StatusNew       Status = "NEW"       // nothing is there
	StatusUnchanged Status = "UNCHANGED" // already byte-for-byte what fft ships
	StatusConflict  Status = "CONFLICT"  // something else is there; --force replaces it
	StatusStale     Status = "STALE"     // fft does not ship this any more; --force removes it
	StatusWritten   Status = "WRITTEN"   // Apply created it
	StatusReplaced  Status = "REPLACED"  // Apply overwrote a CONFLICT
	StatusRemoved   Status = "REMOVED"   // Apply pruned a STALE
)

Jump to

Keyboard shortcuts

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