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 ¶
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.
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 ¶
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 ProjectDir ¶
ProjectDir is ./.claude/skills: the skill for one project, which Claude Code prefers over the personal one when it is working in that directory.
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.
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 ¶
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 ¶
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 ¶
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 )