Closes #44. That issue overstated the risk and this corrects it. The hook is three lines and echoes a string -- no bash-isms, so it cannot fail the way secret-guard appeared to. The real exposure is narrower and duller: it can stop working silently, and its entire job is to be visible. THE ASSERTION THAT MATTERS IS REGISTRATION. A hook file that exists but is absent from hooks.json does nothing at all and looks identical on disk to one that works. So the test derives the command from hooks.json and runs it exactly as registered, substituting CLAUDE_PLUGIN_ROOT the way the harness does, rather than inventing its own invocation. That choice is the lesson from #44's sibling. secret_guard_test.sh piped its hook into `sh` while hooks.json runs it with `bash`; on Debian /bin/sh is dash, which has no `set -o pipefail`, so the suite failed 20/20 in CI against a hook that was FINE in production. The test was wrong, not the hook -- and the earlier commit message for that fix said the shebang decides the interpreter, which hooks.json shows is not true. Deriving the invocation removes the whole class rather than fixing one instance of it. No red phase to report: the hook was already correct, so this is regression pinning, not TDD. Teeth proven by mutation against a throwaway copy instead: - hook runs but prints nothing -> 6 failures - hook removed from hooks.json -> caught, suite stops early - ONE ritual step dropped, rule 0 still named -> caught (9 passed, 1 failed) The third is the one worth having. Losing the whole line is obvious; quietly losing "failing test" from the ritual while the banner still reads correctly is the drift that would actually happen. Bump-Type: patch Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
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
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
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-sourcessobin/doctorcan 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 |
ANTIGRAVITY_SKILLS_JSON |
$HOME/.gemini/config/skills.json (registers AGENTS_SKILLS_DIR as a path entry; needs jq) |
SKILLS_EXTRA_SOURCES |
unset (colon-separated extra source directories) |
The Mistral Vibe target was removed — unused. Antigravity's was removed for the same reason and re-added once it came back into use.
Checking a host
task doctor # or bin/doctor
Reports dangling links, links into a source nobody configured, skills in the checkout with no link, stray entries, leftover per-skill Claude symlinks, and a Claude Code plugin sitting on an older generation than the host already has. 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.
All of that compares copies already on the machine — deliberately, since it runs in a session hook where latency shows. That leaves one case invisible: a host where the checkout, the marketplace clone and the installed plugin all agree, and all three are a release behind origin.
task doctor -- --online # or bin/doctor --online
One git ls-remote against origin, opt-in. Belongs in a scheduled per-host job,
not the session hook. An unreachable remote is reported as its own fault rather
than as staleness — "I could not check" and "you are behind" are different
answers and only one of them is about the host.
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
skills/<short-kebab-name>/. -
Create
skills/<short-kebab-name>/SKILL.mdwith this frontmatter — all four keys are gated bytask check, and the index table is generated from the last three:--- name: <short-kebab-name> surfaces: [claude-code, library] # any of claude-code | library | chat summary: "<one line for the index table>" use_when: "<the trigger, for the index table>" description: <the paragraph the model selects on> ---Then: one-paragraph purpose, triggers ("use when …"), mechanics.
-
Run
task indexand commit the regeneratedSKILLS_INDEX.md. Do not hand-edit the table —task checkregenerates it and fails on a diff. -
Pressure-test it before shipping: run the scenario against a subagent that does not have the skill, write the skill against what that run actually did, then re-run with it. See
superpowers-writing-skillsand #19. -
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.
Mode 2 routing candidates
These notes used to sit in each SKILL.md as a ## Mode 2 Routing Note section.
They changed nothing about how the skill is invoked — several said so outright —
and cost context on every load, so they live here instead. Nothing in the repo
depends on this table; it is a record of which skills looked mechanical enough
to route to a local model, should a routing layer ever exist.
| Skill | Note |
|---|---|
cad-babysit |
Phases 2-4 (reading comments/logs, classifying fault vs outcome) are judgment-heavy and should stay Mode 1. Phase 1 (running cad stuck and grouping its output) is mechanical and a Mode 2 routing candidate once Plan 6 ships the routing pod. |
code-review |
This skill is invoked identically in Mode 1 (cloud Claude) and Mode 2 (client-local with supervisor routing). The routing layer (Plan 6) does not exist yet; treat this skill as Mode 1 only for now. Findings format and severity classification are unchanged across modes. |
debug |
This skill produces high-volume mechanical output (hypothesis enumeration) and is a candidate for Mode 2 routing to a local model in the future. Until Plan 6 ships the routing pod, treat as Mode 1 only. The hypothesis format and discipline are identical regardless of which model generates them. |
feature-spec |
Spec writing requires interpretation of intent and tradeoff reasoning — work where Claude generally outperforms a 9-13B local model. This skill is intentionally not a Mode 2 routing candidate. In Mode 2, the routing layer should leave feature-spec calls on Claude. |
session-retrospective |
Reading long session logs and pulling themes is high-volume, mechanical pattern-recognition work — a strong Mode 2 routing candidate. Until Plan 6 ships the routing pod, treat as Mode 1 only. |
tdd |
This skill is invoked identically whether the agent is running in Mode 1 (cloud Claude, no routing) or Mode 2 (client-local, supervisor routing layer). The routing pod (Plan 6) does not exist yet; until it does, treat this skill as Mode 1 only. The discipline does not change between modes — only the model behind the call. |
trainer |
Trainer is the same shape as session-retrospective — long-context reading, mechanical filtering — and is a Mode 2 routing candidate. Until Plan 6, treat as Mode 1 only. |