Every failure this fleet actually hit was silent. 243 dangling symlinks were
invisible to git because **/.claude/skills/ is in the global gitignore. A
checkout 20 skills behind still resolved, so nothing complained. A loose
secret-hygiene.md loaded in no harness and errored in none. Two machines had
the library missing entirely. Exit status is the signal that was absent.
bin/doctor reports, with a non-zero exit:
- dangling links, anywhere under the harness dirs
- links that RESOLVE but land outside the active checkout — the dangerous
case, because it works while serving stale content
- skill-count mismatch between a harness and the checkout
- stray entries where <name>/SKILL.md was expected
- leftover per-skill Claude symlinks (plugin skills do not override
same-named user skills, so leftovers mean everything loads twice)
- divergence between the plugin copy and the copy the other harnesses link to
Wired as a SessionStart hook with --quiet, so a healthy host prints nothing and
a drifting one says so at the top of the next session. That beats a timer:
no launchd/systemd wiring, and it reaches every host that installs the plugin.
27ms on koala, no network, no git fetch.
Two design points found by the tests, both worth keeping:
- The canonical checkout is whatever ~/.agents/skills resolves to, NOT the
copy doctor runs from. As a plugin hook $0 lives in the plugin cache, which
is legitimately a second copy; judging harness links against it flagged
every one of them as "outside".
- "cd X/.." resolves ".." lexically against the link path and lands back in
~/.agents. Resolve the link first, then take the parent.
tests/doctor_test.sh drives seven fixtures through $HOME and $DOCTOR_CHECKOUT,
so no test touches the real host. Each asserts both the non-zero exit and that
the report names the fault — an exit-code-only assertion would pass while the
message said nothing useful.
Bump-Type: minor
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BeAp5LTscnz2W7Ubt4eJ6m
skills
Engineering skills library — markdown SKILL.md files that plug into any
project or AI-coding harness. Installed via task skills:install in
consumer projects, or directly by install.sh on hosts without Task.
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 for the full catalogue with
descriptions and "use when" triggers.
Install (consumer project)
The expected pattern: each consumer project's Taskfile.yml has a
skills:install target that delegates here. From a project root:
task skills:install
Bare-shell fallback for hosts without Task installed:
curl -fsSL https://gitea.d-ma.be/mathias/skills/raw/branch/main/install.sh | bash
Both forms are idempotent and re-runnable. On every run they:
git clone(orgit pull) this repo into~/.local/share/skills/— the canonical local checkout on every host.- Wire per-skill symlinks into the harnesses found on the host (see Wired harnesses).
Re-run after git pull upstream to pick up new skills or version
changes. CI tags every change as a vX.Y.Z release (see
Versioning).
Wired harnesses
| Harness | Target path | Notes |
|---|---|---|
| Claude Code (global) | ~/.claude/skills/<name> |
Visible in every Claude Code session on the host. |
| Claude Code (per-repo) | <repo>/.claude/skills/<name> |
Created when install is invoked inside a git repo (not the skills repo itself). Gitignored globally via **/.claude/skills/. |
| Crush | ~/.config/crush/skills/<name> |
Charmbracelet Crush — successor to opencode-ai/opencode (archived). |
| Antigravity | ~/.gemini/antigravity/skills/<name> |
Google Antigravity (VS Code extension). Our SKILL.md frontmatter (name + description) is already in the format Antigravity expects, so symlinks suffice — no manifest translation. |
Not wired (and why)
- opencode (
opencode-ai/opencode): archive notice; succeeded by Crush. Its only "skill"-like surface — Custom Commands at~/.config/opencode/commands/— is for user-facing prompt templates, not system-level instructions. Misaligned with how we use skills. Skipped. - gitea-resident agents (cobalt-dingo, agentsquad): they consume
skills via their containing project's
.claude/skillsdirectory (populated by the per-repo wirer when run from the host), or via the brain MCP. No special target needed.
Per-host env-var overrides
Each target path can be overridden by setting the matching env var before
running install.sh:
| Env var | Default |
|---|---|
SKILLS_REPO_URL |
https://gitea.d-ma.be/mathias/skills.git |
SKILLS_REF |
main (set to e.g. v0.1.0 to pin a release) |
SKILLS_CHECKOUT_DIR |
$HOME/.local/share/skills |
CLAUDE_SKILLS_DIR |
$HOME/.claude/skills |
CRUSH_SKILLS_DIR |
$HOME/.config/crush/skills |
ANTIGRAVITY_SKILLS_DIR |
$HOME/.gemini/antigravity/skills |
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.0Bump-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
- Add a directory
<short-kebab-name>/. - Create
<short-kebab-name>/SKILL.md— start with one-paragraph purpose, then triggers ("use when …"), then mechanics. - Add a row to
SKILLS_INDEX.md(description + use-when). - Commit directly to
main. Conventional-commit prefix (feat:/fix:/docs:). AddBump-Type: minorfooter 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.