#!/bin/sh # Tests for bin/generate-index. # # SKILLS_INDEX.md was maintained by hand and drifted: two skills shipped with # no row at all (#15), because their rows lived on branches squash-merged after # a directory reorg. A gate caught drift after the fact; generating the table # from frontmatter removes the class entirely (#29) — the curated summary and # trigger now live next to the skill they describe, which is the same argument # that made a hand-maintained index the wrong shape. set -u ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) SUT="$ROOT/bin/generate-index" pass=0 fail=0 WORK=${TMPDIR:-/tmp}/generate_index_test.$$ if [ ! -f "$SUT" ]; then printf 'FAIL: %s does not exist\n' "$SUT" exit 1 fi cleanup() { rm -rf "$WORK"; } trap cleanup EXIT INT TERM ok() { pass=$((pass + 1)); printf ' ok %s\n' "$1"; } no() { fail=$((fail + 1)); printf ' FAIL %s\n%s\n' "$1" "${2:-}"; } # skill [summary] [use_when] skill() { mkdir -p "$1/skills/$2" { printf -- '---\nname: %s\nsurfaces: %s\n' "$2" "$3" [ $# -lt 4 ] || printf 'summary: "%s"\n' "$4" [ $# -lt 5 ] || printf 'use_when: "%s"\n' "$5" printf -- 'description: d\n---\n' } > "$1/skills/$2/SKILL.md" } # fixture — a root with an index carrying the generated-table markers. fixture() { _f="$WORK/$1" mkdir -p "$_f" cat > "$_f/SKILLS_INDEX.md" <<'IDX' # Engineering Skills Index ## Wiring prose that must survive regeneration IDX printf '%s' "$_f" } f=$(fixture basic) skill "$f" beta '[claude-code, library]' 'B summary' 'when B' skill "$f" alpha '[chat]' 'A summary' 'when A' out=$(sh "$SUT" "$f" 2>&1); rc=$? if [ "$rc" -ne 0 ]; then no 'generates a table' "$out" elif ! grep -q '^| `alpha` | A summary | when A |' "$f/SKILLS_INDEX.md"; then no 'generates a table' "$(cat "$f/SKILLS_INDEX.md")" elif ! grep -q 'prose that must survive regeneration' "$f/SKILLS_INDEX.md"; then no 'content outside the markers survives' "$(cat "$f/SKILLS_INDEX.md")" elif [ "$(grep -n '`alpha`' "$f/SKILLS_INDEX.md" | cut -d: -f1)" -gt "$(grep -n '`beta`' "$f/SKILLS_INDEX.md" | cut -d: -f1)" ]; then no 'rows are sorted' "$(cat "$f/SKILLS_INDEX.md")" else ok 'generates a sorted table and leaves the rest of the file alone' fi # The surfaces a skill ships to are the point of #29, so they are visible. grep -q 'chat' "$f/SKILLS_INDEX.md" \ && ok 'the row names the surfaces' \ || no 'the row names the surfaces' "$(cat "$f/SKILLS_INDEX.md")" # Idempotent: a second run changes nothing. before=$(cat "$f/SKILLS_INDEX.md") sh "$SUT" "$f" >/dev/null 2>&1 [ "$before" = "$(cat "$f/SKILLS_INDEX.md")" ] \ && ok 'regeneration is idempotent' \ || no 'regeneration is idempotent' # --check is the CI gate: clean tree passes. sh "$SUT" --check "$f" >/dev/null 2>&1 \ && ok '--check passes on a generated index' \ || no '--check passes on a generated index' # ...and fails once a skill appears without regenerating. skill "$f" gamma '[library]' 'G summary' 'when G' if sh "$SUT" --check "$f" >/dev/null 2>&1; then no '--check fails on a stale index' else ok '--check fails on a stale index' fi # A skill with no curated summary is an error, not an empty cell: the table # cell is what a reader scans, and a blank one is worse than a missing row. f=$(fixture nosummary) skill "$f" alpha '[library]' out=$(sh "$SUT" "$f" 2>&1); rc=$? if [ "$rc" -eq 0 ]; then no 'a missing summary is an error' "$out" elif ! printf '%s' "$out" | grep -q alpha; then no 'a missing summary is an error' "$out" else ok 'a missing summary is an error naming the skill' fi printf '\ngenerate_index: %d passed, %d failed\n' "$pass" "$fail" [ "$fail" -eq 0 ]