discovery

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Aug 9, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package discovery finds the skills, subagents and commands a machine actually has, and checks the ones batten.yaml claims against them.

batten.yaml names skills and agents by string: `domains.ml.skills: [pytorch-review]`. Those strings rot. A skill gets renamed, a plugin is uninstalled, a teammate clones the repo and never had the agent at all. Claude Code does not complain — the phase simply runs without the skill it was promised, and the run looks exactly like a good one. Discovery exists so that rot surfaces in `batten doctor` at 9am, not inside an unattended fan-out at 3am.

It also runs the other direction: `batten init` proposes a domain→skill mapping from what is actually installed, instead of inventing names the user does not have.

Two things the docs say and the disk does not

Both were confirmed against a real ~/.claude, and both are load-bearing:

  1. A plugin's skills live where its .claude-plugin/plugin.json says they live. The "skills" key is a string OR an array, and an array entry may name a single skill directory rather than a container. ui-ux-pro-max ships seven SKILL.md files under .claude/skills/ and declares exactly one of them; Claude Code exposes only that one. A naive walk would report six skills the user cannot invoke — inventing capability is the same sin as inventing a number.

  2. The DIRECTORY, not the frontmatter `name`, is what a plugin skill is addressed by. claude-mem's skills/version-bump/SKILL.md declares `name: claude-code-plugin-release`, and Claude Code still loads it as `claude-mem:version-bump`. So Item.Name keeps the frontmatter (it is the human-readable label) but Item.Ref — the only string that is safe to write into batten.yaml — is built from the directory. Validate accepts either spelling, because a doctor that cries wolf gets muted.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func PluginDir

func PluginDir(projectDir, name string) (string, bool)

Skills returns every skill visible to projectDir, project first. PluginDir returns the directory an installed plugin lives in, as Claude Code recorded it.

`batten doctor` needs it to check the copy that actually runs. The binary in this repo is not the binary the hooks invoke: hooks call ${CLAUDE_PLUGIN_ROOT}/bin/batten, and the installed tree can be stale, half-written, or line-ending mangled while the source tree is perfect.

func Suggest

func Suggest(domains map[string]spec.Domain, skills []Item) map[string][]string

Suggest maps discovered skills onto the spec's domains by matching the skill's name and description against the domain name and path. Used by `batten init` to PROPOSE a mapping that the user then edits — it is a starting point, never an authority.

The scoring is deliberately blunt (token overlap; a hit in the name outweighs a hit in the description) and the threshold deliberately high: a domain with no confident match gets no key at all. An empty mapping the user must fill in beats a plausible-looking wrong one they never re-read.

Types

type Item

type Item struct {
	Name        string // from frontmatter when present, else the file/dir name
	Slug        string // the file/dir name — what Claude Code actually addresses (see package doc)
	Description string
	Path        string
	Source      Source
	Plugin      string // set when Source == "plugin"
}

Item is one discovered skill, subagent or command.

func Agents

func Agents(projectDir string) ([]Item, error)

Agents returns every subagent visible to projectDir, project first.

func Commands

func Commands(projectDir string) ([]Item, error)

Commands returns every slash command visible to projectDir, project first.

func Skills

func Skills(projectDir string) ([]Item, error)

func (Item) Ref

func (i Item) Ref() string

Ref is how batten.yaml must spell this item: a plugin's skill is addressed `plugin:skill`, everything else by bare name. This is the only spelling `batten init` may write into a spec — Name can disagree with it, and when it does, Name is the one Claude Code ignores.

type Problem

type Problem struct {
	Where string // e.g. `domains.ml.skills` or `gates.qa.skills`
	Ref   string // the name the spec asked for
	Hint  string // nearest existing name, when there is a plausible one
}

Problem is a spec reference that points at something that does not exist.

func Validate

func Validate(sp *spec.Spec, projectDir string) ([]Problem, error)

Validate reports every skill/agent the spec references that cannot be found. This is what turns "my skill got renamed" from a 3am mystery into a doctor warning.

type Source

type Source string
const (
	SourceProject Source = "project"
	SourceUser    Source = "user"
	SourcePlugin  Source = "plugin"
)

Jump to

Keyboard shortcuts

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