Files
skills/Taskfile.yml
T
mathias 83b069afba
CI / Check / Test (push) Successful in 0s
CI / Tag release (push) Successful in 1s
docs(index): add dream + experiment-spec rows, and gate the index
Both skills shipped without an index row. The rows existed — on the feature
branches deleted in #22 — and were dropped by the squash-merge that followed
a directory reorg: the SKILL.md landed under skills/, the row did not. Nothing
errored, the index just under-reported the library to every harness reading it.

Adding two rows by hand fixes today and not tomorrow, so the assertion goes in
`task check` via bin/check-index, following the same pattern as
bin/should-skip-release: a real script with a real test, not untested logic
buried in a Taskfile. It checks both directions — a skill with no row, and a
row naming a skill that no longer exists.

Verified by mutation: deleting the `dream` row turns `task check` red and the
report names it.

Closes #15
Bump-Type: patch
2026-08-29 09:52:03 +02:00

138 lines
5.3 KiB
YAML

version: '3'
# Skills library Taskfile.
#
# This repo is two things at once:
# 1. A Claude Code PLUGIN (skills + hooks + MCP servers), distributed through
# the marketplace this same repo hosts at .claude-plugin/marketplace.json.
# 2. A plain skills library for harnesses with no plugin system — Crush, Pi,
# OpenCode — wired by install.sh as ONE directory symlink per harness.
#
# The wiring loops that used to live here were five near-identical copies that
# drifted; install.sh owns wiring now and this file delegates to it.
vars:
REPO_URL: 'https://git.d-ma.be/mathias/skills.git'
SKILLS_DIR: '{{.TASKFILE_DIR}}/skills'
PLUGIN_MANIFEST: '{{.TASKFILE_DIR}}/.claude-plugin/plugin.json'
tasks:
default:
cmds:
- task: list
list:
desc: 'Print every skill in this repo'
cmds:
- 'ls -1 {{.SKILLS_DIR}}'
silent: true
install:
desc: 'Wire THIS checkout into Crush / Pi / OpenCode. Claude Code uses the plugin instead.'
cmds:
- SKILLS_SKIP_FETCH=1 SKILLS_CHECKOUT_DIR='{{.TASKFILE_DIR}}' bash '{{.TASKFILE_DIR}}/install.sh'
update:
desc: 'Fast-forward this checkout and re-wire.'
cmds:
- git -C '{{.TASKFILE_DIR}}' pull --ff-only
- task: install
doctor:
desc: 'Report drift across every harness on this host. Non-zero exit means something is wrong.'
cmds:
- '{{.TASKFILE_DIR}}/bin/doctor'
validate:
desc: 'Validate the plugin + marketplace manifests with the Claude Code CLI.'
cmds:
- claude plugin validate '{{.TASKFILE_DIR}}'
test:
desc: 'Run the shell test suite (hooks + doctor).'
cmds:
- |
fail=0
for t in '{{.TASKFILE_DIR}}'/tests/*_test.sh; do
[ -f "$t" ] || continue
echo "--- $(basename "$t")"
sh "$t" || fail=1
done
exit $fail
check:
desc: 'Structure gate — layout, frontmatter, manifest/tag parity. Runs in CI.'
cmds:
- |
fail=0
# 1. No stray entries at the repo root. A loose foo.md where
# skills/foo/SKILL.md was meant loads in NO harness and errors in
# none either — it just silently does nothing. (See #23.)
for entry in "{{.TASKFILE_DIR}}"/*; do
base=$(basename "$entry")
case "$base" in
install.sh|Taskfile.yml|README.md|SKILLS_INDEX.md|skills|hooks|bin|tests) continue ;;
esac
echo "ERROR: unexpected entry at repo root: $base" >&2
fail=1
done
# 2. Every skill is a directory with a SKILL.md carrying name + description.
count=0
for dir in "{{.SKILLS_DIR}}"/*; do
name=$(basename "$dir")
if [ ! -d "$dir" ]; then
echo "ERROR: skills/$name is not a directory" >&2; fail=1; continue
fi
if [ ! -f "$dir/SKILL.md" ]; then
echo "ERROR: skills/$name missing SKILL.md" >&2; fail=1; continue
fi
head -n 20 "$dir/SKILL.md" | grep -q '^name:' || { echo "ERROR: skills/$name SKILL.md has no 'name:' frontmatter" >&2; fail=1; }
head -n 20 "$dir/SKILL.md" | grep -q '^description:' || { echo "ERROR: skills/$name SKILL.md has no 'description:' frontmatter" >&2; fail=1; }
count=$((count + 1))
done
# 3. SKILLS_INDEX.md lists exactly the skills that exist. Delegated
# to bin/check-index so the rule is covered by
# tests/check_index_test.sh rather than living untested here.
sh '{{.TASKFILE_DIR}}/bin/check-index' '{{.TASKFILE_DIR}}' || fail=1
# 4. Plugin manifest version must match the latest tag, or installers
# never see the update (Claude Code gates updates on this field).
if [ -f '{{.PLUGIN_MANIFEST}}' ]; then
manifest_v=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' '{{.PLUGIN_MANIFEST}}' | head -1)
tag_v=$(git -C '{{.TASKFILE_DIR}}' describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo '')
if [ -n "$tag_v" ] && [ "$manifest_v" != "$tag_v" ]; then
echo "ERROR: plugin.json version ($manifest_v) != latest tag ($tag_v)" >&2
fail=1
fi
else
echo "ERROR: missing .claude-plugin/plugin.json" >&2; fail=1
fi
if [ $fail -ne 0 ]; then
echo "check FAILED" >&2; exit 1
fi
echo "check OK — $count skills, manifest matches tag"
release:
desc: 'Tag HEAD per Bump-Type in the latest commit footer. Normally CI handles this.'
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"