Files
skills/README.md
T
mathias 025f01aa80
CI / Check / Test (push) Successful in 0s
CI / Tag release (push) Failing after 1s
feat(install): serve several sources instead of displacing them
~/.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
2026-08-29 10:05:46 +02:00

143 lines
5.6 KiB
Markdown

# skills
Engineering skills library — markdown `SKILL.md` files that plug into any
AI-coding harness.
For **Claude Code** this repo is a plugin, installed from the marketplace it
also hosts; that path carries the hooks and MCP servers too. For **Crush, Pi
and OpenCode** it is a plain library wired by `install.sh` as one directory
symlink per harness. See [`SKILLS_INDEX.md`](./SKILLS_INDEX.md#wiring-into-tools).
## What is a skill?
A skill is a single directory containing one or more markdown files. The
canonical entrypoint is `<name>/SKILL.md`. Additional reference material
lives alongside in the same directory (e.g.
`clean-code/references/code-smells.md`). Harnesses load skills on
demand — keep `SKILL.md` short and triggerable; push depth into
references.
See [`SKILLS_INDEX.md`](./SKILLS_INDEX.md) for the full catalogue with
descriptions and "use when" triggers.
## Install
### Claude Code — the plugin
```
/plugin marketplace add https://git.d-ma.be/mathias/skills.git
/plugin install skills@mathias
```
The plugin ships skills, the `secret-guard` + `pre-task-ritual` hooks, and the
brain/gitea/infra MCP servers as one versioned unit. Skills are namespaced
(`skills:tdd`). Updates land when `.claude-plugin/plugin.json`'s `version`
moves — CI bumps it on every release.
### Crush, Pi, OpenCode — `install.sh`
```bash
curl -fsSL https://git.d-ma.be/mathias/skills/raw/branch/main/install.sh | bash
```
Idempotent. It clones or fast-forwards the checkout, then populates one
**managed directory** and gives each harness a single link to it:
| Path | Target |
|---|---|
| `~/.agents/skills/` | a real directory, one symlink per skill (Pi reads this path directly) |
| `~/.agents/skills/<skill>` | `$SKILLS_CHECKOUT_DIR/skills/<skill>`, or another configured source |
| `~/.config/crush/skills` | `~/.agents/skills` |
| `~/.config/opencode/skills` | `~/.agents/skills` |
Total links are N + one per harness — not N x harnesses, which is what #23 was
actually about.
### More than one source of skills
`~/.agents/skills` used to be 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 — and on flamingo the only way to install this library
was to displace Anthropic's official set, which lived in Crush's directory.
Real capability loss dressed up as a tidy-up (#27).
Point `SKILLS_EXTRA_SOURCES` at any number of extra directories, colon
separated, each holding `<skill>/SKILL.md`:
```
SKILLS_EXTRA_SOURCES=~/.config/crush/skills.anthropic bash install.sh
```
- The checkout is always first and always wins a name collision — but the
collision is **logged**, never resolved in silence. Quietly picking one is
how stale content wins.
- Removing a skill from a source removes its link on the next run.
- Anything in the managed directory that is not a symlink is reported and left
alone. install.sh does not delete content it did not create.
- The source list is recorded at `~/.agents/skills-sources` so `bin/doctor`
can tell a link into a known source from a link into a forgotten one.
### Where the checkout must live
`SKILLS_CHECKOUT_DIR` defaults to `~/.local/share/skills`, and that default is
load-bearing: it is **host-local, outside any synced or shared directory**.
On this fleet `~/dev` is an iCloud folder shared by flamingo and iguana, and it
spans two path roots — `/Users/mathias` on the Macs, `/home/mathias` on koala.
A checkout there is a git working tree with two concurrent writers, and any
absolute symlink stored there is broken on one side by construction. That is
what produced 243 dangling links across 12 repos (#23). Do not "fix" the
default to point into `~/dev`.
### Per-host env-var overrides
| Env var | Default |
|---|---|
| `SKILLS_REPO_URL` | `https://git.d-ma.be/mathias/skills.git` |
| `SKILLS_REF` | `main` (set to e.g. `v0.4.0` to pin a release) |
| `SKILLS_CHECKOUT_DIR` | `$HOME/.local/share/skills` |
| `SKILLS_SKIP_FETCH` | unset (`1` wires an existing checkout without touching HEAD) |
| `AGENTS_SKILLS_DIR` | `$HOME/.agents/skills` |
| `CRUSH_SKILLS_DIR` | `$HOME/.config/crush/skills` |
| `OPENCODE_SKILLS_DIR` | `$HOME/.config/opencode/skills` |
| `SKILLS_EXTRA_SOURCES` | unset (colon-separated extra source directories) |
Antigravity and Mistral Vibe targets were removed — unused.
## Checking a host
```bash
task doctor # or bin/doctor
```
Reports dangling links, links resolving into a foreign checkout, skill-count
mismatches, stray entries, and leftover per-skill Claude symlinks. Non-zero
exit on drift. It also runs at session start via the plugin, so a drifting
host says so rather than waiting to be discovered.
## Versioning
Every commit to `main` is auto-tagged as a semver release by the
`.gitea/workflows/release.yml` action. Bump direction follows the commit
footer:
- `Bump-Type: major` → `X.0.0`
- `Bump-Type: minor` → `0.Y.0`
- *anything else* → `0.0.Z` (patch, the default)
Consumer projects can pin a version by setting `SKILLS_REF=v0.1.0`
before `task skills:install`; default is `main` for always-current.
## Contributing a skill
1. Add a directory `<short-kebab-name>/`.
2. Create `<short-kebab-name>/SKILL.md` — start with one-paragraph
purpose, then triggers ("use when …"), then mechanics.
3. Add a row to `SKILLS_INDEX.md` (description + use-when).
4. Commit directly to `main`. Conventional-commit prefix
(`feat:` / `fix:` / `docs:`). Add `Bump-Type: minor` footer when
adding a new skill or expanding scope; default patch covers edits.
This repo is markdown-only and public — never include secrets, client
names, or anything under NDA in a skill.