SpuntoSpunto/@spunto/build

Skills layout

What buildSkillsScript leaves on disk — clones under ~/.spunto/skills/<id>/, one symlink per skill in each discovery directory, a ledger, and a [skills] log prefix.

A skill is a directory that holds a SKILL.md, plus whatever scripts and references sit next to it. A source is a git repository that holds several skills under one path. Organization skills reach a worker through one generated script, buildSkillsScript. It runs as the skills phase of the first setup (after dotfiles, before the project's clones), and again as a refresh before each delegated task, because a recycled worker never replays its setup.

The script runs twice on the same disk, possibly from two different package versions. On its second run, it has to recognize what its first run left. The layout below is therefore a format: the script's earlier self, the harness and the people reading the log all depend on it. Module reference: script.

On disk

.links
spunto-ref
deploy → ~/.spunto/skills/<source-id>/skills/deploy
repos → ~/.spunto/skills/<source-id>/skills/repos
my-own-skill (not ours: never touched)
PathWhat it is
~/.spunto/skills/The clone root. SKILLS_DIR = ".spunto/skills", relative to the home. It is harness-neutral on purpose: it is not under .claude.
~/.spunto/skills/<id>/A shallow (--depth 1) clone of one source. <id> is the source's stable id, restricted to [A-Za-z0-9_-]. Anything else is refused rather than escaped, because the script removes this path.
~/.spunto/skills/<id>/.git/spunto-refThe ref (branch or tag) the clone was made from, written without a newline. It is empty for the default branch.
~/.spunto/skills/.linksThe ledger: the absolute path of every symlink this script posed on its last run, one per line.
<discovery dir>/<skill name>A symlink to ~/.spunto/skills/<id>/<path>/<skill name>, one per skill, never one for the whole directory.

Discovery directories

Discovery directories are where the harness looks for user-level skills. The caller passes them as shell expressions, which are evaluated inside the worker, because only the worker knows its environment. A member can set CLAUDE_CONFIG_DIR as a secret. For Claude Code, the expression is:

"${CLAUDE_CONFIG_DIR:-$HOME/.claude}/skills"

Spunto Cloud gets this from each harness pack's declaration and sends the union of all packs' directories. Every retained skill is linked into each discovery directory.

What a run does

Sync each source

If <id>/.git exists but spunto-ref differs from the requested ref, the clone is deleted and made again, because a fast-forward between two branches means nothing. Without a clone, the script runs git clone --quiet --depth 1 [--branch <ref>], resets origin to the credential-free URL, and writes spunto-ref. With a clone, it fetches the ref, first through the given URL (which carries the organization's read credential) and then through origin (the worker's credential helper), and runs merge --ff-only FETCH_HEAD.

Every sub-directory of <id>/<path> that contains a SKILL.md is a skill, named after its directory. It is retained if it is in include, or if linkByDefault is set and it is not in exclude. include wins over exclude. For each discovery directory:

  • a target that this script posed before (a symlink listed in the ledger) is kept, and repointed if needed;
  • a target that exists and is not ours (dotfiles, the member's own skill) is left alone and reported;
  • a name already linked by an earlier source in this run is skipped and reported: the first source wins;
  • otherwise, ln -s creates the link.

Take down what is no longer wanted

Every path in the old ledger that was not linked again in this run is removed, if it is still a symlink. Then the new list replaces the ledger. Any clone directory under ~/.spunto/skills/ whose name is not a current source id is deleted (the organization removed that source).

The ledger is what makes refresh safe. The script never infers from the filesystem what it owns. A symlink is only removed if the script recorded it, so nothing that a person put in a discovery directory is ever touched, even if it looks like one of ours.

The [skills] log prefix

The script never fails. A skill that could not be fetched makes for a worse worker, not a broken one. Every problem is printed on stdout with a [skills] prefix, and the script ends with exit 0. The setup phase that runs it adds its own fallback: … || echo "[skills] ✗ skills script exited with $?".

These are the lines it prints, verbatim, with shell variables in place:

success and audit
[skills] <label> @ <ref|default branch> → <full commit sha>
[skills] linked <name> → <discovery dir>/<name>
[skills] unlinked <name>
[skills] removed source <id>
[skills] <label>: ref changed to '<ref|default branch>' — cloning again
warnings (⚠): the run continues
[skills] ⚠ <label>: fetch failed — keeping <short sha>
[skills] ⚠ <label>: cannot fast-forward (local changes?) — keeping <short sha>
[skills] ⚠ <id>: no directory '<path>' in the source
[skills] ⚠ <name>: already linked from another source — skipped
[skills] ⚠ <name>: <discovery dir>/<name> already exists — kept, not linked
errors (✗): the source or the run is skipped, exit is still 0
[skills] ✗ cannot write <home>/.spunto/skills
[skills] ✗ <label>: nothing to clone from
[skills] ✗ <label>: clone failed — its skills are not linked

<label> is the source's human label (owner/name) and is never a URL, so that a credential cannot end up in the log. The @ … → <sha> line logs the exact commit that was materialized, for the audit trail: these scripts run with the worker's secrets.

Credentials

  • Each source's fetchUrl may carry a credential. It can be overridden at run time by the environment variable SPUNTO_SKILL_FETCH_URL_<index> (skillFetchUrlEnv(index)), so that a caller whose command text is kept in a history can pass the credential out of band.
  • After a clone, origin points at originUrl, which is credential-free. Anything that happens later in the worker (an agent fixing a skill and pushing a branch) goes through the credential helper as the member, not with the organization's clone token.
  • The script runs as the workspace user, never as root, so every file it creates belongs to that user. It sets GIT_TERMINAL_PROMPT=0, so a missing credential fails instead of hanging.

Compatibility

Changing SKILLS_DIR or the .links filename would make the next refresh on an existing worker treat its own earlier links as foreign. They would be kept and reported as "already exists — kept, not linked" on every run, and under a new SKILLS_DIR the old clones would be orphaned. Renaming spunto-ref is milder, but it still has an effect: every clone made from a non-empty ref would look like a ref change and be cloned again once. Treat these names the way you treat names and paths.

Edit on GitHub

On this page