Some checks failed
release / tag (push) Has been cancelled
Phase 2 of mathias/skills extraction. Adds two new per-tool wirers alongside Claude Code: - Crush — ~/.config/crush/skills/<name>/SKILL.md (charmbracelet/crush, successor to opencode-ai/opencode which is archived) - Antigravity — ~/.gemini/antigravity/skills/<name>/SKILL.md (Google VS Code extension). Our SKILL.md frontmatter (name + description) is already in the format Antigravity expects — no manifest translation step needed. Both wirers added to Taskfile.yml (install:crush + install:antigravity) and install.sh (wire_crush + wire_antigravity). The aggregate `install` target now calls all four targets (claude:global, claude:repo, crush, antigravity). Idempotent symlinks, same shape as the Claude Code wirer. Per-host env overrides documented in README: SKILLS_REF for tag pinning, CRUSH_SKILLS_DIR + ANTIGRAVITY_SKILLS_DIR for non-default paths. Skipped: - opencode (opencode-ai/opencode): archived, succeeded by Crush; its Custom Commands surface is user prompt templates, not system skills - gitea-resident agents: consume via per-repo .claude/skills or brain MCP, no special target needed Bump-Type: minor
176 lines
6.5 KiB
YAML
176 lines
6.5 KiB
YAML
version: '3'
|
|
|
|
# Skills library Taskfile.
|
|
#
|
|
# Primary entry point for project + host installation. Consumer projects
|
|
# do NOT depend on this file directly — they ship their own
|
|
# `skills:install` target that delegates here via `install.sh`. This
|
|
# Taskfile is invoked by maintainers of the skills repo itself, plus by
|
|
# install.sh during the post-clone wire step.
|
|
|
|
vars:
|
|
REPO_URL: 'https://gitea.d-ma.be/mathias/skills.git'
|
|
CHECKOUT_DIR: '{{.HOME}}/.local/share/skills'
|
|
CLAUDE_GLOBAL_DIR: '{{.HOME}}/.claude/skills'
|
|
CRUSH_DIR: '{{.HOME}}/.config/crush/skills'
|
|
ANTIGRAVITY_DIR: '{{.HOME}}/.gemini/antigravity/skills'
|
|
# Anything at the repo root that is NOT a skill directory. Both
|
|
# Taskfile + install.sh share this exclusion regex.
|
|
NON_SKILL_ENTRIES: '^(Taskfile.yml|install.sh|README.md|SKILLS_INDEX.md|.gitea|.git$)$'
|
|
|
|
tasks:
|
|
|
|
default:
|
|
cmds:
|
|
- task: list
|
|
|
|
list:
|
|
desc: 'Print every skill in this repo'
|
|
cmds:
|
|
- 'ls -1 {{.TASKFILE_DIR}} | grep -Ev "^(Taskfile.yml|install.sh|README.md|SKILLS_INDEX.md|.gitea|.git$)$" || true'
|
|
silent: true
|
|
|
|
install:
|
|
desc: 'Wire skills into every supported harness (Claude Code, Crush, Antigravity).'
|
|
cmds:
|
|
- task: install:claude:global
|
|
- task: install:claude:repo
|
|
- task: install:crush
|
|
- task: install:antigravity
|
|
|
|
install:claude:global:
|
|
desc: 'Per-skill symlinks under ~/.claude/skills/<name> — visible in every Claude Code project on this host.'
|
|
cmds:
|
|
- mkdir -p {{.CLAUDE_GLOBAL_DIR}}
|
|
- |
|
|
for skill in $(ls -1 "{{.TASKFILE_DIR}}" | grep -Ev "{{.NON_SKILL_ENTRIES}}"); do
|
|
target="{{.TASKFILE_DIR}}/${skill}"
|
|
link="{{.CLAUDE_GLOBAL_DIR}}/${skill}"
|
|
if [ -L "$link" ] || [ -e "$link" ]; then
|
|
current=$(readlink "$link" 2>/dev/null || true)
|
|
if [ "$current" = "$target" ]; then
|
|
continue
|
|
fi
|
|
rm -rf "$link"
|
|
fi
|
|
ln -s "$target" "$link"
|
|
echo "linked $link → $target"
|
|
done
|
|
|
|
install:claude:repo:
|
|
desc: 'Per-skill symlinks under $(pwd)/.claude/skills/<name> when invoked inside a git repo.'
|
|
cmds:
|
|
- |
|
|
if ! git rev-parse --show-toplevel >/dev/null 2>&1; then
|
|
echo "not in a git repo — skipping per-repo wire"; exit 0
|
|
fi
|
|
repo=$(git rev-parse --show-toplevel)
|
|
if [ "$repo" = "{{.TASKFILE_DIR}}" ]; then
|
|
echo "running from skills repo itself — skipping per-repo wire"; exit 0
|
|
fi
|
|
target_dir="$repo/.claude/skills"
|
|
mkdir -p "$target_dir"
|
|
for skill in $(ls -1 "{{.TASKFILE_DIR}}" | grep -Ev "{{.NON_SKILL_ENTRIES}}"); do
|
|
target="{{.TASKFILE_DIR}}/${skill}"
|
|
link="${target_dir}/${skill}"
|
|
if [ -L "$link" ] || [ -e "$link" ]; then
|
|
current=$(readlink "$link" 2>/dev/null || true)
|
|
if [ "$current" = "$target" ]; then
|
|
continue
|
|
fi
|
|
rm -rf "$link"
|
|
fi
|
|
ln -s "$target" "$link"
|
|
echo "linked $link → $target"
|
|
done
|
|
|
|
install:crush:
|
|
desc: 'Per-skill symlinks under ~/.config/crush/skills/<name> — visible in every Crush session on this host.'
|
|
cmds:
|
|
- mkdir -p {{.CRUSH_DIR}}
|
|
- |
|
|
for skill in $(ls -1 "{{.TASKFILE_DIR}}" | grep -Ev "{{.NON_SKILL_ENTRIES}}"); do
|
|
target="{{.TASKFILE_DIR}}/${skill}"
|
|
link="{{.CRUSH_DIR}}/${skill}"
|
|
if [ -L "$link" ] || [ -e "$link" ]; then
|
|
current=$(readlink "$link" 2>/dev/null || true)
|
|
if [ "$current" = "$target" ]; then
|
|
continue
|
|
fi
|
|
rm -rf "$link"
|
|
fi
|
|
ln -s "$target" "$link"
|
|
echo "linked $link → $target"
|
|
done
|
|
|
|
install:antigravity:
|
|
desc: 'Per-skill symlinks under ~/.gemini/antigravity/skills/<name>. SKILL.md frontmatter (name + description) is already antigravity-compatible.'
|
|
cmds:
|
|
- mkdir -p {{.ANTIGRAVITY_DIR}}
|
|
- |
|
|
for skill in $(ls -1 "{{.TASKFILE_DIR}}" | grep -Ev "{{.NON_SKILL_ENTRIES}}"); do
|
|
target="{{.TASKFILE_DIR}}/${skill}"
|
|
link="{{.ANTIGRAVITY_DIR}}/${skill}"
|
|
if [ -L "$link" ] || [ -e "$link" ]; then
|
|
current=$(readlink "$link" 2>/dev/null || true)
|
|
if [ "$current" = "$target" ]; then
|
|
continue
|
|
fi
|
|
rm -rf "$link"
|
|
fi
|
|
ln -s "$target" "$link"
|
|
echo "linked $link → $target"
|
|
done
|
|
|
|
update:
|
|
desc: 'git pull the canonical checkout then re-run install.'
|
|
cmds:
|
|
- |
|
|
if [ ! -d "{{.CHECKOUT_DIR}}/.git" ]; then
|
|
echo "no canonical checkout at {{.CHECKOUT_DIR}} — run install.sh first"; exit 1
|
|
fi
|
|
git -C "{{.CHECKOUT_DIR}}" pull --ff-only
|
|
- task: install
|
|
|
|
release:
|
|
desc: 'Tag HEAD per Bump-Type in the latest commit footer. Normally CI handles this — run locally only when CI is unavailable.'
|
|
cmds:
|
|
- |
|
|
latest=$(git -C "{{.TASKFILE_DIR}}" describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
|
|
major=$(echo "$latest" | sed -E 's/^v([0-9]+)\.([0-9]+)\.([0-9]+)$/\1/')
|
|
minor=$(echo "$latest" | sed -E 's/^v([0-9]+)\.([0-9]+)\.([0-9]+)$/\2/')
|
|
patch=$(echo "$latest" | sed -E 's/^v([0-9]+)\.([0-9]+)\.([0-9]+)$/\3/')
|
|
bump=$(git -C "{{.TASKFILE_DIR}}" log -1 --pretty=%B | grep -iE "^Bump-Type:" | head -1 | awk '{print tolower($2)}')
|
|
case "$bump" in
|
|
major) major=$((major + 1)); minor=0; patch=0 ;;
|
|
minor) minor=$((minor + 1)); patch=0 ;;
|
|
*) patch=$((patch + 1)) ;;
|
|
esac
|
|
next="v${major}.${minor}.${patch}"
|
|
echo "tagging $next (previous $latest, bump=${bump:-patch})"
|
|
git -C "{{.TASKFILE_DIR}}" tag -a "$next" -m "release $next"
|
|
echo "now: git push origin $next"
|
|
|
|
check:
|
|
desc: 'Sanity check — every skill dir has a SKILL.md, no stray files at root.'
|
|
cmds:
|
|
- |
|
|
missing=0
|
|
for skill in $(ls -1 "{{.TASKFILE_DIR}}" | grep -Ev "^(Taskfile.yml|install.sh|README.md|SKILLS_INDEX.md|.gitea|.git$)$"); do
|
|
dir="{{.TASKFILE_DIR}}/${skill}"
|
|
if [ ! -d "$dir" ]; then
|
|
echo "ERROR: $skill is not a directory" >&2
|
|
missing=$((missing + 1))
|
|
continue
|
|
fi
|
|
if [ ! -f "$dir/SKILL.md" ]; then
|
|
echo "ERROR: $dir missing SKILL.md" >&2
|
|
missing=$((missing + 1))
|
|
fi
|
|
done
|
|
if [ $missing -gt 0 ]; then
|
|
echo "$missing skill(s) failed structure check" >&2
|
|
exit 1
|
|
fi
|
|
echo "all skills have SKILL.md"
|