Authoring Skills
When you write or change a skill, you usually want to try it inside a real project that consumes it — without committing and pushing every edit, and without touching the project's ai.json/ai.lock. spm link does exactly that.
The local-development loop
The skill (or plugin) must already be declared in the consuming project. Point it at your working copy:
cd ~/work/my-app # a project that depends on `reviewer`
spm link reviewer ../my-skills/reviewer # the dir holding SKILL.md
# ...edit ../my-skills/reviewer/SKILL.md (no commit needed)...
spm install # re-copy the latest local content
spm unlink reviewer # back to the version pinned in ai.lockFor a plugin, link its root (the directory with .claude-plugin/plugin.json and skills/):
spm link design-system ../design-system/plugins/cdsspm link materializes immediately into every configured target. There is no file watching: re-run spm install (or any other syncing command) after an edit. spm unlink --all drops every link in the scope.
What a link changes — and what it doesn't
- Machine-local. The override is stored under
$SPM_HOME, never in the checkout:$SPM_HOME/links/<project>-<hash>.json(keyed by the project's path), or$SPM_HOME/links.jsonwith-g. A repository therefore can't ship link state — a.spm/links.jsoncommitted to a repo is ignored — so only runningspm linkon your machine activates a local source. Moving the project leaves its links behind; re-runspm linkthere. ai.jsonandai.lockstay byte-identical. The lock keeps pinning the declared git source, so teammates, CI andspm install --lockedare unaffected and your diff stays clean.link/unlinknever writeai.lock, so if it is missing or out of sync withai.jsonthey ask you to runspm installfirst.- Keep the working copy outside spm's output. A directory that overlaps what a target writes or deletes — e.g. Codex's
.agents/skills/<name>, or the project root (which contains the output) — is rejected, since syncing would wipe it or copy the output into itself. So is a path that reaches it through a symlink inside the output; link the clone's real location instead. - Same safety rules. Linked content is copied (never symlinked),
.gitis skipped, symlinks inside the directory may not point outside it, and it must pass the same content scan as fetched content. - Transitive dependencies (with
"resolveTransitive": true) are still read from the locked commit's nestedai.json, so linking a root does not change the resolved graph or the lock. - Visible.
spm listandspm status(and their--jsonoutput) show each active link aslinked → <dir>.
Paths
In project scope a relative directory is stored exactly as you typed it and is resolved against the project root (the directory holding ai.json, where project-scoped commands must be run), so the link stays portable if you move the projects together. With -g the path is stored absolute. Forward and back slashes both work on Windows (spm link reviewer ..\my-skills\reviewer).
When things go stale
spm removea linked dependency: the leftover link is ignored with a warning (and flagged byspm status) until youspm unlinkit.- The linked directory is moved or deleted: syncs fail with an error naming the link; re-link it with
spm link <name> <dir>or drop it withspm unlink <name>.