shuhari

Concepts

Six terms describe all of shuhari: skill, source, skill pattern, target, install choice and machine config. This page defines each one, then says how shuhari decides what it may change.

Skill

A skill is a directory that contains a file named exactly SKILL.md. The file starts with YAML frontmatter that has a name and a description. A skill without them gets the diagnostic malformed manifest.

Source

A source is a git repository that contains skills. Each machine lists its sources in its machine config. There are two kinds:

KindDeclared withWhat shuhari does
working-copy sourcepath, and git if it has a remoteReads the directory you work in. It never moves its HEAD to a pin. It can receive imports.
tool-managed sourcegit onlyClones it to ~/.local/share/shuhari/sources/<name> and updates it.

A source with a rev is a pinned source: rev is a full commit ID. shuhari outdated lists pinned sources that have newer upstream commits, and shuhari bump writes the newest commit into rev.

A source without git works on this machine only. sync does not fetch it, and doctor warns that other machines cannot get it and prints the commands to add a remote. shuhari never creates a remote.

Skill pattern

A skill pattern is a glob, relative to the source root, that names skill directories. For example, personal/* names every directory directly inside personal/. A pattern cannot be absolute and cannot contain ...

Commands read only the directories that match the patterns. init searches a repository once and proposes one pattern per parent directory of the skills it finds. A source with no patterns has no skills, and doctor warns about it.

Target

A target is a skill directory that an agent reads. A new config starts with two:

[targets]
claude = { kind = "host", path = "~/.claude/skills" }
agents = { kind = "host", path = "~/.agents/skills" }

shuhari links skills into targets as symlinks. Two targets cannot resolve to the same directory. Targets are flat, so two selected skills with the same directory name cannot both go into one target: apply refuses both and names both sources. Fix the conflict with shuhari exclude.

Install choice and tag

An install choice says which skills go into which targets. It uses selectors. A selector is source:skill-glob or tag:<name>.

[tags]
lang = ["agent-skills:rust-*", "agent-skills:go"]

[[install]]
skills  = ["agent-skills:rust-style", "superpowers:brainstorming"]
targets = ["claude"]

[[install]]
tags    = ["lang"]
targets = ["claude", "agents"]

A tag is a named group of selectors. An exclude removes one skill from the selection, everywhere or for one target.

Machine config

Each machine has its own machine config at ~/.config/shuhari/config.toml. It holds the machine name, targets, sources, skill patterns, tags, install choices, excludes and local declarations. Two machines can list different sources and choose different skills.

shuhari edits the file in place: when a command changes one value, every other line stays as you wrote it. See Config for every key.

Ownership

An entry in a target is an owned link when it is a symlink that points into a source and its name is the name of the skill directory it points at. apply changes owned links only. It creates, replaces or removes each one to match the install choices.

apply removes an owned link in two cases only:

  • No install choice for that target selects the skill any more. This is your explicit choice, so it applies whatever the state of the source.
  • The source is healthy, and the skill directory no longer exists in it (missing upstream). A link into a source that is not healthy is never removed for this reason.

Immediately before each change, apply checks its precondition again. Before it replaces or removes a link, it confirms that the link is still owned. Run shuhari plan to see the changes before you apply them.

Entry states

shuhari scan gives each entry one state and zero or more diagnostics. The summary counts each entry in the first group that applies, in this order:

GroupEntries
needs attentionState stray link, unreadable or not imported. Or a diagnostic broken link, name conflict, malformed manifest or review due, on an entry of any state.
linkedState linked: an owned link into a source.
kept localState local: wip, local: machine or app-owned.
not managedState not a skill, unowned link or alias link.
StateMeaning
linkedAn owned link into a source.
stray linkAn owned link whose destination matches no skill pattern of its source. It is replaced, never removed.
not importedA real directory in a target with a SKILL.md, not declared local.
local: wip, local: machineA path declared local in the config.
app-ownedA path that an app manages itself, on a built-in list.
alias linkA symlink into a source whose name differs from its destination's name. Not owned.
unowned linkA symlink that points outside every source.
not a skillA directory without a SKILL.md.
unreadableThe entry could not be read: permissions or an I/O error.

Local skills

Some skills belong in a target but not in a source. Declare them local, and shuhari stops reporting them as not imported:

  • Work in progress (shuhari keep <path> --wip): a skill you are still changing. Add --review-after <YYYY-MM-DD>, and after that date scan reports it as review due.
  • Bound to this machine (shuhari keep <path> --machine): a skill that only makes sense here, for example one that drives a macOS app.

When a skill is ready to share, shuhari import <path> --to <source> moves it into a working-copy source and leaves a link in its place.

Sync with git

shuhari has no server. Git is the sync mechanism. You commit and push skills in your working-copy sources as usual. On each machine, shuhari sync updates every source that has a remote, then runs apply.

A pinned tool-managed source stays at its rev until you run shuhari bump. shuhari status shows each source's HEAD, pin state, uncommitted changes and commits behind, from the last fetch. It never fetches.