feat(install): serve several sources instead of displacing them
CI / Check / Test (push) Successful in 0s
CI / Tag release (push) Failing after 1s

~/.agents/skills was a single link straight at this checkout. That encoded an
assumption nobody had stated — that this checkout is the only source of skills
on the host. On flamingo it was not: Crush's directory held Anthropic's
official set, and the only way to install this library was to move that set
aside. A capability loss dressed up as a tidy-up.

The canonical location becomes a real directory of per-skill symlinks, fed by
the checkout plus anything in SKILLS_EXTRA_SOURCES. Harnesses keep their one
directory link, so the count is N + one per harness rather than N x harnesses,
which is what #23 was actually about.

The three requirements #27 named, each with a test:
- collisions are logged with both paths and the checkout wins, never resolved
  in silence;
- removing a skill from a source prunes its link;
- anything in the managed directory that is not our symlink is reported and
  left in place — the installer does not delete content it did not create.

doctor learns the same model: sources are recorded at ~/.agents/skills-sources,
a link into a configured source is healthy, a link into an unknown one is not,
and the old count check becomes a per-skill check because a per-skill layout
can lose one skill without changing any total. The pre-#27 wiring is named as
legacy rather than reported as damage. count_skills needed find -L; without it
symlinked skill directories count as zero.

Verified live on koala: migrated in place to 42 per-skill links, then a second
source added a 43rd alongside them, then a re-run without that source pruned it
back to 42. Only doctor finding throughout was the genuine stale plugin.

Closes #27
Bump-Type: minor
This commit is contained in:
2026-08-29 10:05:46 +02:00
parent eee62e4fc0
commit 025f01aa80
6 changed files with 443 additions and 66 deletions
+14 -9
View File
@@ -70,17 +70,21 @@ everything load twice.
### Crush, Pi, OpenCode — run `install.sh`
These have no plugin system, so they get symlinks — but **one directory link
each**, never one per skill:
These have no plugin system, so they get symlinks — but through one managed
directory, never one link per skill per harness:
```
~/.agents/skills -> $SKILLS_CHECKOUT_DIR/skills (Pi reads this directly)
~/.agents/skills/ (real directory; Pi reads it directly)
~/.agents/skills/tdd -> $SKILLS_CHECKOUT_DIR/skills/tdd
~/.agents/skills/xlsx -> $SKILLS_EXTRA_SOURCES entry
~/.config/crush/skills -> ~/.agents/skills
~/.config/opencode/skills -> ~/.agents/skills
```
Adding a skill therefore wires it everywhere with no re-run, and a broken link
is one broken link instead of 42.
Total links are N + one per harness. `SKILLS_EXTRA_SOURCES` (colon-separated
directories) lets a second library — Anthropic's official set, for instance —
live alongside this one instead of being displaced by it; name collisions are
logged rather than silently resolved, and the checkout wins (#27).
```
curl -fsSL https://git.d-ma.be/mathias/skills/raw/branch/main/install.sh | bash
@@ -94,10 +98,11 @@ absolute link stored there is broken on one side by construction. See #23.
### Check a host
`task doctor` (or `bin/doctor`) reports dangling links, links resolving into a
foreign checkout, count mismatches, stray entries, and leftover Claude
symlinks. It also runs automatically at session start via the plugin, so drift
announces itself instead of waiting to be found.
`task doctor` (or `bin/doctor`) reports dangling links, links into a source
nobody configured, skills in the checkout with no link, stray entries, leftover
Claude symlinks, and a Claude Code plugin sitting on an older generation than
the host already has. It also runs automatically at session start via the
plugin, so drift announces itself instead of waiting to be found.
## Skill Relationships