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
138 lines
5.3 KiB
YAML
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"
|