forked from bchanot/claude
fix(gstack): one helper-link tree for every hardcoded path, honest doctor stats
gstack skills hardcode ~/.claude/skills/gstack/<path> for 83 shared assets (bin, scripts/jargon-list.json, ETHOS.md, */sections, review/specialists, make-pdf/dist, lib/diagram-render/dist, freeze/bin...) but only bin and browse/dist were linked: make-pdf and diagram failed on every run, cso and plan-*-review could not read their sections, the freeze hook exited 127. lib/gstack-links.sh links every top-level entry except SKILL.md, skips non-skill dirs holding a nested SKILL.md (browser-skills, openclaw, node_modules), removes the global symlink gstack ./setup plants and refuses a destination inside the submodule. link.sh, install-plugins.sh and update-all.sh all call it (three hand-copied blocks gone). doctor.sh counted 34 skills (find without -L) and zero chars for block scalar descriptions; lib/doctor-skills.sh reuses the census parser and counts through the symlinks. Plugin constants re-based on measured values; install-plugins.sh notes why frontend-design@claude-plugins-official and brightdata-plugin@synced stay off and describes security-guidance truthfully.
This commit is contained in:
Executable
+91
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env bash
|
||||
# lib/tests/doctor-skills.test.sh — lib/doctor-skills.sh's
|
||||
# skill_catalog_stats(): an inline scalar description, a `|` block
|
||||
# scalar, a `>-` folded block, a SKILL.md with no description, a
|
||||
# symlinked skill dir (counted, matching Python glob.glob's symlink
|
||||
# behavior), and an absent skills dir ("0 0", rc 0 — doctor.sh runs
|
||||
# under `set -euo pipefail` and must never abort on this check).
|
||||
set -u
|
||||
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
LIB="$ROOT/lib/doctor-skills.sh"
|
||||
pass=0; fail=0
|
||||
check() { if [ "$2" = "$3" ]; then pass=$((pass+1)); else fail=$((fail+1));
|
||||
printf 'FAIL %s: got[%s] want[%s]\n' "$1" "$2" "$3"; fi; }
|
||||
|
||||
WORK="$(mktemp -d)"; trap 'rm -rf "$WORK"' EXIT
|
||||
SKILLS="$WORK/skills"
|
||||
REAL="$WORK/real-skill"
|
||||
mkdir -p "$SKILLS/inline" "$SKILLS/pipe-block" "$SKILLS/fold-block" \
|
||||
"$SKILLS/no-desc" "$REAL"
|
||||
|
||||
cat > "$SKILLS/inline/SKILL.md" <<'EOF'
|
||||
---
|
||||
name: inline
|
||||
description: "Inline description, sixteen."
|
||||
---
|
||||
body
|
||||
EOF
|
||||
|
||||
cat > "$SKILLS/pipe-block/SKILL.md" <<'EOF'
|
||||
---
|
||||
name: pipe-block
|
||||
description: |
|
||||
Block scalar description
|
||||
spanning two lines.
|
||||
---
|
||||
body
|
||||
EOF
|
||||
|
||||
cat > "$SKILLS/fold-block/SKILL.md" <<'EOF'
|
||||
---
|
||||
name: fold-block
|
||||
description: >-
|
||||
Folded block description
|
||||
on two lines too.
|
||||
---
|
||||
body
|
||||
EOF
|
||||
|
||||
cat > "$SKILLS/no-desc/SKILL.md" <<'EOF'
|
||||
---
|
||||
name: no-desc
|
||||
---
|
||||
body
|
||||
EOF
|
||||
|
||||
cat > "$REAL/SKILL.md" <<'EOF'
|
||||
---
|
||||
name: symlinked
|
||||
description: "Symlinked skill description."
|
||||
---
|
||||
body
|
||||
EOF
|
||||
ln -s "$REAL" "$SKILLS/symlinked"
|
||||
|
||||
# ── hand-computed expectations (mirrors extract_description()'s scalar
|
||||
# and block-scalar handling) ──
|
||||
INLINE_DESC="Inline description, sixteen."
|
||||
PIPE_DESC="Block scalar description spanning two lines."
|
||||
FOLD_DESC="Folded block description on two lines too."
|
||||
SYM_DESC="Symlinked skill description."
|
||||
EXP_COUNT=5
|
||||
EXP_CHARS=$((${#INLINE_DESC} + ${#PIPE_DESC} + ${#FOLD_DESC} + ${#SYM_DESC}))
|
||||
|
||||
# shellcheck source=../doctor-skills.sh disable=SC1091
|
||||
source "$LIB"
|
||||
|
||||
out="$(skill_catalog_stats "$SKILLS")"
|
||||
rc=$?
|
||||
got_count="${out%% *}"
|
||||
got_chars="${out##* }"
|
||||
check T1-rc "$rc" 0
|
||||
check T1-count "$got_count" "$EXP_COUNT"
|
||||
check T1-chars "$got_chars" "$EXP_CHARS"
|
||||
|
||||
# ── T2: absent dir — "0 0", rc 0 ──
|
||||
out2="$(skill_catalog_stats "$WORK/does-not-exist")"
|
||||
rc2=$?
|
||||
check T2-rc "$rc2" 0
|
||||
check T2-zeroes "$out2" "0 0"
|
||||
|
||||
printf 'PASS=%s FAIL=%s\n' "$pass" "$fail"; [ "$fail" -eq 0 ]
|
||||
Executable
+101
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env bash
|
||||
# lib/tests/gstack-links.test.sh — lib/gstack-links.sh's
|
||||
# link_gstack_helpers(): whole-class mirroring of a gstack skill dir
|
||||
# (SKILL.md excluded), whole-dir symlink for a clean non-skill dir/file,
|
||||
# skip-whole for a non-skill dir hiding a nested SKILL.md
|
||||
# (browser-skills/, openclaw/-style), skip-by-name for `.git*` and
|
||||
# `node_modules`, idempotent re-run (echoes 0, nothing changes), a stale
|
||||
# dst-is-symlink-to-src planted by gstack ./setup (removed, dst becomes
|
||||
# a real dir, nothing written into src), and a dst path that would
|
||||
# resolve inside src (refused, rc 1, nothing created). Covers contract
|
||||
# criterion 3 (whole class) and the r4 confirmation-pass fixtures.
|
||||
set -u
|
||||
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
LIB="$ROOT/lib/gstack-links.sh"
|
||||
pass=0; fail=0
|
||||
check() { if [ "$2" = "$3" ]; then pass=$((pass+1)); else fail=$((fail+1));
|
||||
printf 'FAIL %s: got[%s] want[%s]\n' "$1" "$2" "$3"; fi; }
|
||||
|
||||
WORK="$(mktemp -d)"; trap 'rm -rf "$WORK"' EXIT
|
||||
SRC="$WORK/src"
|
||||
DST="$WORK/dst"
|
||||
|
||||
# ── fixture src: one skill dir (browse), one nested skill-dir tree
|
||||
# (review, with a sub-directory of its own), plain shared assets
|
||||
# (bin/, ETHOS.md), the top-level gstack SKILL.md itself, vcs metadata,
|
||||
# and the two non-skill-dir-hiding-a-nested-SKILL.md cases (other/deep,
|
||||
# node_modules/pkg) ──
|
||||
mkdir -p "$SRC/bin" "$SRC/browse/dist" "$SRC/review/specialists" \
|
||||
"$SRC/.git" "$SRC/other/deep" "$SRC/node_modules/pkg"
|
||||
echo x > "$SRC/bin/x"
|
||||
echo ethos > "$SRC/ETHOS.md"
|
||||
echo skill > "$SRC/SKILL.md"
|
||||
echo browse-skill > "$SRC/browse/SKILL.md"
|
||||
echo browse-bin > "$SRC/browse/dist/browse"
|
||||
echo review-skill > "$SRC/review/SKILL.md"
|
||||
echo checklist > "$SRC/review/checklist.md"
|
||||
echo spec-a > "$SRC/review/specialists/a.md"
|
||||
echo head > "$SRC/.git/HEAD"
|
||||
echo nested > "$SRC/other/deep/SKILL.md"
|
||||
echo pkg-skill > "$SRC/node_modules/pkg/SKILL.md"
|
||||
|
||||
# shellcheck source=../gstack-links.sh disable=SC1091
|
||||
source "$LIB"
|
||||
|
||||
# ── T1: first run mirrors the whole class, exposes no SKILL.md ──
|
||||
n1=$(link_gstack_helpers "$SRC" "$DST" 2>/dev/null)
|
||||
check T1-bin-resolves \
|
||||
"$([ -f "$DST/bin/x" ] && cat "$DST/bin/x" || echo missing)" x
|
||||
check T1-ethos-resolves \
|
||||
"$([ -f "$DST/ETHOS.md" ] && cat "$DST/ETHOS.md" || echo missing)" ethos
|
||||
check T1-browse-dist-resolves \
|
||||
"$([ -f "$DST/browse/dist/browse" ] && cat "$DST/browse/dist/browse" \
|
||||
|| echo missing)" browse-bin
|
||||
check T1-review-checklist-resolves \
|
||||
"$([ -f "$DST/review/checklist.md" ] && cat "$DST/review/checklist.md" \
|
||||
|| echo missing)" checklist
|
||||
check T1-review-specialists-resolves \
|
||||
"$([ -f "$DST/review/specialists/a.md" ] \
|
||||
&& cat "$DST/review/specialists/a.md" || echo missing)" spec-a
|
||||
check T1-no-skillmd-anywhere \
|
||||
"$(find -L "$DST" -name SKILL.md 2>/dev/null | wc -l | tr -d ' ')" 0
|
||||
check T1-no-dotgit "$([ -e "$DST/.git" ] && echo present || echo absent)" \
|
||||
absent
|
||||
check T1-no-other "$([ -e "$DST/other" ] && echo present || echo absent)" \
|
||||
absent
|
||||
check T1-no-node-modules \
|
||||
"$([ -e "$DST/node_modules" ] && echo present || echo absent)" absent
|
||||
check T1-count-positive "$([ "$n1" -gt 0 ] && echo yes || echo no)" yes
|
||||
|
||||
# ── T2: idempotent re-run — echoes 0, tree unchanged ──
|
||||
n2=$(link_gstack_helpers "$SRC" "$DST" 2>/dev/null)
|
||||
check T2-echoes-zero "$n2" 0
|
||||
check T2-bin-still-resolves \
|
||||
"$([ -f "$DST/bin/x" ] && cat "$DST/bin/x" || echo missing)" x
|
||||
|
||||
# ── T3: dst is a symlink to src (gstack ./setup's stale-link case) —
|
||||
# removed, dst becomes a real dir, nothing written into src ──
|
||||
DST3="$WORK/dst-symlinked"
|
||||
ln -s "$SRC" "$DST3"
|
||||
n3=$(link_gstack_helpers "$SRC" "$DST3" 2>/dev/null)
|
||||
check T3-dst-is-real-dir "$([ -d "$DST3" ] && [ ! -L "$DST3" ] \
|
||||
&& echo yes || echo no)" yes
|
||||
check T3-bin-resolves \
|
||||
"$([ -f "$DST3/bin/x" ] && cat "$DST3/bin/x" || echo missing)" x
|
||||
check T3-nothing-written-in-src \
|
||||
"$(find "$SRC" -type l 2>/dev/null | wc -l | tr -d ' ')" 0
|
||||
check T3-count-positive "$([ "$n3" -gt 0 ] && echo yes || echo no)" yes
|
||||
|
||||
# ── T4: dst path resolves inside src — refused, rc 1, nothing created ──
|
||||
DST4="$SRC/helpers"
|
||||
out4=$(link_gstack_helpers "$SRC" "$DST4" 2>&1 >/dev/null)
|
||||
rc4=$?
|
||||
n4=$(link_gstack_helpers "$SRC" "$DST4" 2>/dev/null)
|
||||
check T4-rc "$rc4" 1
|
||||
check T4-echoes-zero "$n4" 0
|
||||
check T4-nothing-created "$([ -e "$DST4" ] && echo present || echo absent)" \
|
||||
absent
|
||||
check T4-warns "$(printf '%s' "$out4" | grep -qi 'refusing' \
|
||||
&& echo yes || echo no)" yes
|
||||
|
||||
printf 'PASS=%s FAIL=%s\n' "$pass" "$fail"; [ "$fail" -eq 0 ]
|
||||
Reference in New Issue
Block a user