mathias 4d6ebb626f fix(check): read the session_log payload, not the file
A traceability probe falsified this gate four hours after I shipped it. The
check was `grep -q 'session_id' "$dir/SKILL.md"` — file-scoped. So the probe
stripped session_id from all three documented payloads in tdd/SKILL.md, added
one prose line saying "the tool has a required session_id field", and the gate
returned 0. The exact regression it exists to prevent was back in the file, and
the gate said nothing.

The duration_ms rule was no better: it matched one spelling, the historical one.
Both of these passed — `"duration_ms": <wall-clock>` with a JSON-quoted key, and
`duration_ms: elapsed wall-clock ms` with no angle brackets. It was a regression
test against the nine strings that existed that morning, described in the commit
message as a rule.

Now each documented payload is checked on its own line and reported with a line
number, and duration_ms is rejected in any spelling unless it carries an actual
number — the objection was always to a placeholder no agent can fill, not to the
field.

Five new cases, each one a probe from that run: prose mentioning the word while
the payload omits it, one bad payload among several, both duration_ms spellings,
and a real measurement being allowed.

Also fixed while writing them: my first regex had `[[:space:]]*` before a
negated class that did not exclude whitespace, so the star matched empty and the
space itself tripped the check — a real measurement failed. The tests caught it
immediately, which is the argument for writing them from the probes rather than
from the intent.

Refs #36, #39
Bump-Type: patch
2026-08-29 15:34:11 +02:00

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-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

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.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 skills/<short-kebab-name>/.

  2. Create skills/<short-kebab-name>/SKILL.md with this frontmatter — all four keys are gated by task 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.

  3. Run task index and commit the regenerated SKILLS_INDEX.md. Do not hand-edit the table — task check regenerates it and fails on a diff.

  4. 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-skills and #19.

  5. 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.

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.
S
Description
Engineering skills library — markdown SKILL.md files installed into any project or harness via task skills:install
Readme
853 KiB
Languages
Shell 84%
JavaScript 9.5%
HTML 3.9%
TypeScript 2.6%