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 ManifestName = ".fft-skill.json"
ManifestName is the file in the skill's directory that records what fft wrote there.
It is what lets an upgrade tell fft's own old text from the user's edit. Every release that changes a word of the skill changes the bytes of an installed copy against the new one, and comparing bytes alone cannot say whose change that was — so without a record, every upgrade asked for --force, and an agent, which has no terminal to be asked on, could never take one.
A sidecar rather than more frontmatter: it records every file, not only SKILL.md, and it keeps a list of hashes out of the one block an agent reads before anything else. A dotfile, so that it is not mistaken for part of the skill by anything listing the directory. It is fft's: never a stray, never a row in the plan, and not evidence that a directory is somebody else'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 ErrMalformed = errors.New("malformed skill document")
ErrMalformed reports an installed SKILL.md whose frontmatter does not parse.
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 Current ¶ added in v0.9.0
Current reports whether an installed skill describes the fft that is running.
A build that did not come from a release tag has no version to hold the skill to, and counts every skill as current — the same rule, for the same reason, as the update check: a developer running what they are working on does not need telling to reinstall it. A skill with no stamp at all predates stamping, and is not current: that is the install that most needs the hint.
func Document ¶
func Document() string
Document is the whole of SKILL.md, frontmatter and version stamp included: what `fft skill show` prints, what `fft skill install` writes, and what an agent that is not Claude Code wants appended to its own context file.
func FS ¶
FS is the skill's tree as it is written in the repository: SKILL.md without the version stamp that installing adds. The drift spec and the docs generator read it, and neither has any use for which fft built the binary they run in — the generated guide pages would otherwise change on every release. What is written to disk, and what `fft skill show` prints, is Document.
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"`
Metadata map[string]string `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
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.
Metadata is the format's own free-form map, and the one place fft writes into the frontmatter: the version of fft that installed the skill. See Meta.Version.
func Installed ¶ added in v0.9.0
Installed reads the frontmatter of the skill installed under root — the SKILL.md in root/fft — and reports false when there is none there.
A SKILL.md whose frontmatter does not parse is ErrMalformed rather than an unversioned skill: fft never wrote one like that, so it is somebody's edit, and calling it "from an older fft" would send them to an install that refuses to touch it. Any other error is the file being unreadable, and is returned as the filesystem gave it, path and all.
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 and fft's own untouched old files 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 removed and replaced nothing of anybody's. The one file it may still write is its own record, ManifestName, when that is missing.
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, a file an older fft wrote and nobody has touched since — 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 tells those two apart, and it cannot tell apart the two ways a file comes to differ: fft's own text from an older release, and the user's edit. Every release that changes a word of the skill makes an untouched install look edited — and if that asked for --force, a notice saying "run fft skill install" would send an agent, with no terminal to be asked on, straight into a refusal. So fft keeps a record of what it wrote (ManifestName), and knows every text it shipped before it kept one ([legacy]). A file that matches either is OUTDATED, or OBSOLETE if fft no longer ships it, and changes without a question. Anything else that differs is still CONFLICT or STALE.
const ( StatusNew Status = "NEW" // nothing is there StatusUnchanged Status = "UNCHANGED" // already byte-for-byte what fft ships StatusOutdated Status = "OUTDATED" // what an older fft wrote, untouched since; replaced without asking StatusConflict Status = "CONFLICT" // something else is there; --force replaces it StatusObsolete Status = "OBSOLETE" // an older fft wrote this and fft no longer ships it; removed without asking StatusStale Status = "STALE" // not fft's, or edited since; --force removes it StatusWritten Status = "WRITTEN" // Apply created it StatusUpdated Status = "UPDATED" // Apply restamped an OUTDATED StatusReplaced Status = "REPLACED" // Apply overwrote a CONFLICT StatusRemoved Status = "REMOVED" // Apply pruned a STALE )