Files
skills/tests/doctor_test.sh
T
mathiasandClaude Opus 5 abd81f939a
release / tag (push) Successful in 1s
fix(doctor): report a real harness directory as unmanaged, not as "outside"
Found on flamingo during the first Mac install. ~/.config/crush/skills held
real directories from a pre-plugin install, and doctor said:

  FAIL outside: ~/.config/crush/skills resolves to ~/.config/crush/skills,
  outside the active checkout ~/.local/share/skills/skills

Naming a directory as its own resolution target is true and useless. Worse, it
pointed at the wrong fix: "outside" means a link into a foreign checkout, which
you repair by re-pointing the link. A real directory is repaired by moving it
aside and re-running install.sh — install.sh already refuses to delete it,
correctly, since it did not create it and cannot know what is in there.

Now reported as its own class, with the skill count so it is obvious whether
the directory holds a stale copy of the library or something unique that
belongs in the repo:

  FAIL unmanaged: <dir> is a real directory, not a link into the checkout —
  it holds N unmanaged skill(s); move it aside and re-run install.sh

The distinction matters because content in that directory reaches exactly one
harness and cannot receive new skills — the failure mode is silent divergence,
which is the same family as everything else this tool exists to catch.

Bump-Type: patch

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BeAp5LTscnz2W7Ubt4eJ6m
2026-08-26 07:53:14 +02:00

138 lines
5.7 KiB
Bash

#!/bin/sh
# Tests for bin/doctor.
#
# Every case here is a real state this fleet was found in on 2026-08-23, and
# every one of them was SILENT — nothing errored, git status was clean, the
# harness simply ran with the wrong skills or none at all. The point of doctor
# is that none of these can be silent again, so each fixture asserts both a
# non-zero exit AND that the report names the fault.
#
# Fixtures drive doctor through $HOME and $DOCTOR_CHECKOUT, so no test touches
# the real host.
set -u
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
DOCTOR="$ROOT/bin/doctor"
pass=0
fail=0
WORK=${TMPDIR:-/tmp}/doctor_test.$$
if [ ! -f "$DOCTOR" ]; then
printf 'FAIL: %s does not exist\n' "$DOCTOR"
exit 1
fi
cleanup() { rm -rf "$WORK"; }
trap cleanup EXIT INT TERM
# new_fixture <name> — builds $WORK/<name>/{ck,home} with a 2-skill checkout
# and an empty fake HOME. Echoes the fixture root.
new_fixture() {
_f="$WORK/$1"
mkdir -p "$_f/ck/skills/alpha" "$_f/ck/skills/beta" "$_f/home"
printf -- '---\nname: alpha\ndescription: a\n---\n' > "$_f/ck/skills/alpha/SKILL.md"
printf -- '---\nname: beta\ndescription: b\n---\n' > "$_f/ck/skills/beta/SKILL.md"
printf '%s' "$_f"
}
# wire_clean <fixture> — the correct wiring: one canonical link, harnesses
# pointing at it.
wire_clean() {
mkdir -p "$1/home/.agents" "$1/home/.config/crush" "$1/home/.config/opencode"
ln -sfn "$1/ck/skills" "$1/home/.agents/skills"
ln -sfn "$1/home/.agents/skills" "$1/home/.config/crush/skills"
ln -sfn "$1/home/.agents/skills" "$1/home/.config/opencode/skills"
}
# assert_fails <fixture> <substring> <label>
# Note: doctor must be invoked directly here, not via a helper called in a
# command substitution — $? from inside $( ) belongs to the subshell and the
# exit status, which is the whole point of these assertions, would be lost.
assert_fails() {
_out=$(HOME="$1/home" DOCTOR_CHECKOUT="$1/ck" sh "$DOCTOR" 2>&1); rc=$?
if [ "$rc" -eq 0 ]; then
fail=$((fail + 1))
printf ' FAIL %s\n doctor exited 0; drift went undetected\n' "$3"
elif ! printf '%s' "$_out" | grep -qi -- "$2"; then
fail=$((fail + 1))
printf ' FAIL %s\n exited %s but report never mentions "%s"\n got: %s\n' "$3" "$rc" "$2" "$_out"
else
pass=$((pass + 1))
printf ' ok %s\n' "$3"
fi
}
assert_clean() {
_out=$(HOME="$1/home" DOCTOR_CHECKOUT="$1/ck" sh "$DOCTOR" 2>&1); rc=$?
if [ "$rc" -eq 0 ]; then
pass=$((pass + 1)); printf ' ok %s\n' "$2"
else
fail=$((fail + 1)); printf ' FAIL %s\n exited %s on a healthy fixture\n got: %s\n' "$2" "$rc" "$_out"
fi
}
# --- 1. healthy host stays silent -------------------------------------------
f=$(new_fixture clean); wire_clean "$f"
assert_clean "$f" 'clean fixture exits 0'
# --- 2. dangling link (the 243-link case, gitignored and invisible) ----------
f=$(new_fixture dangling); wire_clean "$f"
ln -sfn "$f/ck/skills-that-moved" "$f/home/.agents/skills"
assert_fails "$f" 'dangling' 'dangling canonical link is reported'
# --- 3. resolves, but to a DIFFERENT checkout (the silent-stale case) --------
# Worse than dangling: it works, so nothing complains, while serving content
# from a checkout that is months behind.
f=$(new_fixture stale)
mkdir -p "$f/other/skills/alpha" "$f/other/skills/beta"
printf -- '---\nname: alpha\ndescription: a\n---\n' > "$f/other/skills/alpha/SKILL.md"
printf -- '---\nname: beta\ndescription: b\n---\n' > "$f/other/skills/beta/SKILL.md"
wire_clean "$f"
ln -sfn "$f/other/skills" "$f/home/.agents/skills"
assert_fails "$f" 'outside' 'link into a foreign checkout is reported'
# --- 4. count mismatch (a harness seeing fewer skills than the checkout) -----
f=$(new_fixture count)
mkdir -p "$f/home/.config/crush/skills/alpha"
printf -- '---\nname: alpha\ndescription: a\n---\n' > "$f/home/.config/crush/skills/alpha/SKILL.md"
mkdir -p "$f/home/.agents"
ln -sfn "$f/ck/skills" "$f/home/.agents/skills"
assert_fails "$f" 'count' 'skill-count mismatch is reported'
# --- 5. stray entry (the loose secret-hygiene.md that loaded nowhere) --------
f=$(new_fixture stray); wire_clean "$f"
printf 'orphan\n' > "$f/ck/skills/loose-note.md"
assert_fails "$f" 'stray' 'stray non-directory entry is reported'
# --- 6. legacy Claude symlinks alongside the plugin (duplicate skills) -------
f=$(new_fixture dupe); wire_clean "$f"
mkdir -p "$f/home/.claude/skills"
ln -sfn "$f/ck/skills/alpha" "$f/home/.claude/skills/alpha"
assert_fails "$f" 'legacy' 'leftover per-skill Claude symlinks are reported'
# --- 7b. harness dir is a REAL directory, not a link into the checkout -------
# Found on flamingo 2026-08-26: ~/.config/crush/skills held real directories
# from a pre-plugin install. The old message read "resolves to
# <path>, outside the active checkout" — naming the directory as its own
# resolution target, which is true but useless. It must say the dir is not a
# link at all, because the fix (move it aside, re-run install.sh) is different
# from the fix for a link into a foreign checkout.
f=$(new_fixture realdir)
mkdir -p "$f/home/.agents" "$f/home/.config/crush/skills/alpha"
ln -sfn "$f/ck/skills" "$f/home/.agents/skills"
printf -- '---\nname: alpha\ndescription: a\n---\n' > "$f/home/.config/crush/skills/alpha/SKILL.md"
assert_fails "$f" 'not a link' 'real directory is reported as unmanaged, not as "outside"'
# --- 7. dangling link inside a harness dir (not the canonical link itself) --
# Exercises the per-entry scan rather than the canonical-link check: this is
# the shape the 243 dead links actually had.
f=$(new_fixture ghost); wire_clean "$f"
mkdir -p "$f/home/.claude/skills"
ln -sfn "$f/ck/skills/vanished" "$f/home/.claude/skills/vanished"
assert_fails "$f" 'dangling' 'dangling link inside a harness dir is reported'
printf '\ndoctor: %d passed, %d failed\n' "$pass" "$fail"
[ "$fail" -eq 0 ]