Second half of #29. The index was hand-maintained and had already drifted once: dream and experiment-spec shipped with no row, their rows lost to a squash-merge after a directory reorg. bin/check-index caught that after the fact; generating removes the class. The curated summary and trigger move into each SKILL.md as `summary:` and `use_when:`, carried over verbatim from the existing table rather than derived from `description:` — description is a paragraph written for model selection, and truncating it would have produced a worse table than the one it replaced. Knowledge now lives once, next to the skill it describes, which is the argument that made a hand-maintained index the wrong shape to begin with. The table is written between markers, so the wiring docs, the relationships tree and the trigger table below it are untouched — those are prose, not data. Rows are alphabetical now; the conceptual grouping the old order carried lives in the relationships tree, which is the better place for it. A fourth column shows each skill's surfaces, which is what makes #29's field visible rather than merely enforced. bin/check-index and its test are deleted: a generated table cannot drift in either direction, so `generate-index --check` subsumes both of its assertions. Verified by mutation — deleting the tdd row turns `task check` red with "SKILLS_INDEX.md is out of date". Closes #29 Bump-Type: minor
114 lines
3.7 KiB
Bash
114 lines
3.7 KiB
Bash
#!/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 <root> <name> <surfaces> [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 <name> — 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
|
|
|
|
<!-- BEGIN skills-table -->
|
|
<!-- END skills-table -->
|
|
|
|
## 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 ]
|