From 78b9b59ecaaa276a14f12549141d36b4ee6402c7 Mon Sep 17 00:00:00 2001 From: Mathias Date: Sun, 23 Aug 2026 14:35:38 +0200 Subject: [PATCH] docs+ci: document the plugin path, bump plugin.json version on release MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI now writes the computed semver into .claude-plugin/plugin.json before tagging, committed with [skip-release] so it does not re-trigger the tagger. Claude Code gates plugin updates on that field: without the bump, installed hosts never see a new tag no matter how many releases go out. `task check` enforces manifest/tag equality so the two cannot silently diverge. Docs rewritten around the two delivery paths, and the checkout location is now stated as load-bearing rather than incidental: SKILLS_CHECKOUT_DIR defaults to ~/.local/share/skills because that is host-local and outside any synced directory. The previous docs told you to clone into ~/dev, which on this fleet is an iCloud folder shared by two Macs spanning two path roots — a git working tree with concurrent writers, and absolute links that are broken on one side by construction. That advice is what #23 is about; it is now inverted with the reason attached. Antigravity and Vibe removed from the docs along with their targets. Bump-Type: patch Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01BeAp5LTscnz2W7Ubt4eJ6m --- .gitea/workflows/release.yml | 17 +++++++ README.md | 94 ++++++++++++++++++++---------------- SKILLS_INDEX.md | 62 ++++++++++++++++++------ 3 files changed, 117 insertions(+), 56 deletions(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 2ce5741..b1fc653 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -62,6 +62,23 @@ jobs: set -euo pipefail git config user.name "${GITEA_ACTOR}" git config user.email "${GITEA_ACTOR}@noreply.gitea.d-ma.be" + + # Claude Code gates plugin updates on plugin.json's version field: + # if it does not move, installed hosts never see the new tag. Bump + # it in the same commit train so the tag and the manifest agree — + # `task check` enforces that equality on every run. + manifest=.claude-plugin/plugin.json + version="${TAG#v}" + current=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$manifest" | head -1) + if [ "$current" != "$version" ]; then + sed -i "s/\"version\"[[:space:]]*:[[:space:]]*\"$current\"/\"version\": \"$version\"/" "$manifest" + git add "$manifest" + # [skip-release] stops this push from re-triggering the tagger. + git commit -m "chore(release): plugin.json version $version [skip-release]" + git push origin HEAD:main + echo "bumped $manifest to $version" + fi + git tag -a "$TAG" -m "release $TAG" # gitea-actions checkout uses an x-access-token URL; reuse it. git push origin "$TAG" diff --git a/README.md b/README.md index b796123..3ef61db 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,12 @@ # 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. +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? @@ -16,65 +20,73 @@ references. See [`SKILLS_INDEX.md`](./SKILLS_INDEX.md) for the full catalogue with descriptions and "use when" triggers. -## Install (consumer project) +## Install -The expected pattern: each consumer project's `Taskfile.yml` has a -`skills:install` target that delegates here. From a project root: +### Claude Code — the plugin -```bash -task skills:install +``` +/plugin marketplace add https://git.d-ma.be/mathias/skills.git +/plugin install skills@mathias ``` -Bare-shell fallback for hosts without Task installed: +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://gitea.d-ma.be/mathias/skills/raw/branch/main/install.sh | bash +curl -fsSL https://git.d-ma.be/mathias/skills/raw/branch/main/install.sh | bash ``` -Both forms are idempotent and re-runnable. On every run they: +Idempotent. It clones or fast-forwards the checkout, then creates **three** +symlinks — one directory link per harness, never one per skill: -1. `git clone` (or `git pull`) this repo into `~/.local/share/skills/` — - the **canonical local checkout** on every host. -2. Wire per-skill symlinks into the harnesses found on the host (see - [Wired harnesses](#wired-harnesses)). +| Link | Target | +|---|---| +| `~/.agents/skills` | `$SKILLS_CHECKOUT_DIR/skills` (Pi reads this path directly) | +| `~/.config/crush/skills` | `~/.agents/skills` | +| `~/.config/opencode/skills` | `~/.agents/skills` | -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](#versioning)). +Adding a skill wires it everywhere with no re-run. -## Wired harnesses +### Where the checkout must live -| Harness | Target path | Notes | -|---|---|---| -| **Claude Code (global)** | `~/.claude/skills/` | Visible in every Claude Code session on the host. | -| **Claude Code (per-repo)** | `/.claude/skills/` | Created when `install` is invoked inside a git repo (not the skills repo itself). Gitignored globally via `**/.claude/skills/`. | -| **Crush** | `~/.config/crush/skills/` | Charmbracelet Crush — successor to `opencode-ai/opencode` (archived). | -| **Antigravity** | `~/.gemini/antigravity/skills/` | Google Antigravity (VS Code extension). Our `SKILL.md` frontmatter (`name` + `description`) is already in the format Antigravity expects, so symlinks suffice — no manifest translation. | +`SKILLS_CHECKOUT_DIR` defaults to `~/.local/share/skills`, and that default is +load-bearing: it is **host-local, outside any synced or shared directory**. -### 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/skills` directory - (populated by the per-repo wirer when run from the host), or via the - brain MCP. No special target needed. +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 -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_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` | -| `CLAUDE_SKILLS_DIR` | `$HOME/.claude/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` | -| `ANTIGRAVITY_SKILLS_DIR` | `$HOME/.gemini/antigravity/skills` | +| `OPENCODE_SKILLS_DIR` | `$HOME/.config/opencode/skills` | + +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 diff --git a/SKILLS_INDEX.md b/SKILLS_INDEX.md index 2d1d204..f984028 100644 --- a/SKILLS_INDEX.md +++ b/SKILLS_INDEX.md @@ -47,23 +47,55 @@ This index lists all available engineering skills. Load the full SKILL.md on dem ## Wiring into tools -This repo **is** the canonical home. Wire every skill into each harness with the -**`install.sh`** script (idempotent, per-skill symlinks) — either the one-shot -`curl -fsSL https://git.d-ma.be/mathias/skills/raw/branch/main/install.sh | bash`, -or, from a clone, `SKILLS_CHECKOUT_DIR="$PWD" bash install.sh`. (The `task -install` targets are currently broken — see issue — use `install.sh`.) +This repo is the canonical home, and it is delivered two different ways. -- **Claude Code (global)** — `~/.claude/skills/` → `/`. -- **Claude Code (per-repo)** — `/.claude/skills/` when `task install` - is run inside another git repo (excluded via `~/.config/git/ignore`). -- **Crush** — `~/.config/crush/skills/`. -- **Antigravity** — `~/.gemini/antigravity/skills/`. -- **Mistral Vibe** — `~/.vibe/skills/.md` → `//SKILL.md` - (flat `.md` layout). +### Claude Code — install the plugin -Individual targets are also runnable: `task install:claude:global`, -`install:crush`, `install:antigravity`, `install:vibe`. `task update` pulls the -canonical checkout then re-wires. +This repo hosts a marketplace containing exactly one plugin: itself. The plugin +carries the skills **plus** the `secret-guard` and `pre-task-ritual` hooks and +the brain/gitea/infra MCP servers — none of which a symlink can deliver. + +``` +/plugin marketplace add https://git.d-ma.be/mathias/skills.git +/plugin install skills@mathias +``` + +Skills are namespaced: `tdd` becomes `skills:tdd`. Updates arrive when +`.claude-plugin/plugin.json`'s `version` moves, which CI does on every release. +Remove any leftover `~/.claude/skills/` symlinks from the pre-plugin era — +plugin skills do **not** override same-named user skills, so leftovers make +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: + +``` +~/.agents/skills -> $SKILLS_CHECKOUT_DIR/skills (Pi reads this directly) +~/.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. + +``` +curl -fsSL https://git.d-ma.be/mathias/skills/raw/branch/main/install.sh | bash +``` + +`SKILLS_CHECKOUT_DIR` defaults to `~/.local/share/skills` and **must stay +outside `~/dev`**. On this fleet `~/dev` is an iCloud folder shared by both +Macs and spans two path roots (`/Users/mathias` vs `/home/mathias` on koala); +a checkout there means a git working tree with two concurrent writers, and any +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. ## Skill Relationships