Skip to content

Commit 84764e7

Browse files
test(skills): add A/B usability harness comparing the same prompt with and without skills, plus static skill validation
1 parent 0dfad19 commit 84764e7

5 files changed

Lines changed: 158 additions & 0 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,4 @@ Homestead.json
2828
.env.production
2929
.phpactor.json
3030
auth.json
31+
test/results/

test/README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Test environment
2+
3+
A minimal environment proving that the **skills are usable by an agent** — not that the Openapi APIs work. The core test is an A/B comparison: the same prompt run by an agent *without* the skills and by an agent *with* them, so the difference the skills make is directly observable.
4+
5+
## 1. A/B usability comparison — `run_comparison.sh`
6+
7+
**Problem given to both agents** ([prompt.txt](prompt.txt)): "All we know about a supplier is its VAT number 12485671007 — find the company name, status, registered office, and the province/region of its ZIP, using the Openapi platform with the credentials in the environment."
8+
9+
The harness:
10+
11+
1. creates two throwaway workspaces under `test/results/<timestamp>/`:
12+
- **bare** — empty folder, no skills
13+
- **skilled** — identical, but with `skills/` installed as project skills (`.claude/skills/`)
14+
2. runs `claude -p` headless with the same prompt and the same allowed tools in both
15+
3. saves both transcripts and prints a comparison table: wall time, turns, cost, and whether a `SOLUTION` block was produced
16+
17+
```bash
18+
export OPENAPI_EMAIL=you@example.com
19+
export OPENAPI_KEY=...
20+
bash test/run_comparison.sh
21+
```
22+
23+
What to look for in the transcripts: the *bare* agent has to discover authentication (Basic vs Bearer, the scope format `METHOD:host/path`), guess endpoint names, and typically burns turns on 401/404s. The *skilled* agent reads `openapi-auth`, `openapi-company`, `openapi-geo` and goes straight to: scoped token → `IT-start/{vat}``cap/{zip}` → solution.
24+
25+
Notes: both agents make real (cheap, but paid) API calls; results land in `test/results/` which is git-ignored.
26+
27+
## 2. Static validation — `validate_skills.sh`
28+
29+
Sanity lint for every `skills/*/SKILL.md` — no credentials needed, exits non-zero on violations:
30+
31+
- frontmatter present with `name` (matching the folder) and `description` (≤ 1024 chars)
32+
- no references to `knowledge/` (skills must be self-contained — see repo README)
33+
- mentions `openapi-auth` for authentication
34+
35+
```bash
36+
bash test/validate_skills.sh
37+
```

test/prompt.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
All we know about a supplier is its Italian VAT number: 12485671007.
2+
3+
Using the Openapi (openapi.com) platform APIs, find out:
4+
1. the company name and whether it is still active
5+
2. the address of its registered office
6+
3. the province and region its ZIP code belongs to
7+
8+
Account credentials are available in the environment variables OPENAPI_EMAIL and OPENAPI_KEY. Work only from the command line (curl/jq). Be frugal: paid requests cost wallet credit, so make the minimum number of API calls needed.
9+
10+
End your answer with a block:
11+
SOLUTION:
12+
company: <name> (<status>)
13+
office: <address>
14+
territory: <province>, <region>

test/run_comparison.sh

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#!/usr/bin/env bash
2+
# A/B usability test for the skills.
3+
#
4+
# The same prompt (prompt.txt) is given to two headless Claude Code agents:
5+
# A. "bare" — empty workspace, NO skills installed
6+
# B. "skilled" — identical workspace WITH skills/ installed as project skills
7+
#
8+
# What is being tested is NOT the Openapi APIs, but whether the skills let an
9+
# agent attack the problem and drive it straight to the solution: compare the
10+
# two transcripts (number of calls, wrong turns, time, correctness).
11+
#
12+
# Requirements: claude CLI, OPENAPI_EMAIL + OPENAPI_KEY in the environment.
13+
set -u
14+
15+
TEST_DIR="$(cd "$(dirname "$0")" && pwd)"
16+
REPO_DIR="$(dirname "$TEST_DIR")"
17+
RUN_DIR="$TEST_DIR/results/$(date +%Y%m%d-%H%M%S)"
18+
PROMPT="$(cat "$TEST_DIR/prompt.txt")"
19+
20+
command -v claude >/dev/null || { echo "ERROR: claude CLI not found"; exit 1; }
21+
[ -n "${OPENAPI_EMAIL:-}" ] && [ -n "${OPENAPI_KEY:-${OPENAPI_API_KEY:-}}" ] \
22+
|| { echo "ERROR: OPENAPI_EMAIL and OPENAPI_KEY must be set"; exit 1; }
23+
24+
mkdir -p "$RUN_DIR/bare" "$RUN_DIR/skilled/.claude"
25+
cp -r "$REPO_DIR/skills" "$RUN_DIR/skilled/.claude/skills"
26+
27+
run_agent() { # $1 = variant dir name
28+
local variant="$1"
29+
echo "=== Running '$variant' agent (this may take a few minutes)..."
30+
( cd "$RUN_DIR/$variant" && \
31+
/usr/bin/time -o ../"$variant".time -f '%e' \
32+
claude -p "$PROMPT" \
33+
--allowedTools "Bash,Read,Glob,Grep" \
34+
--output-format json \
35+
> ../"$variant".json 2> ../"$variant".err )
36+
jq -r '.result // "NO RESULT"' "$RUN_DIR/$variant.json" > "$RUN_DIR/$variant.answer.md"
37+
}
38+
39+
run_agent bare
40+
run_agent skilled
41+
42+
# ------------------------------------------------------------------ report
43+
report() { # $1 = variant
44+
local v="$1" j="$RUN_DIR/$1.json"
45+
printf '%-10s | %8ss | %5s turns | $%-7s | solution: %s\n' \
46+
"$v" \
47+
"$(cat "$RUN_DIR/$v.time" 2>/dev/null || echo '?')" \
48+
"$(jq -r '.num_turns // "?"' "$j")" \
49+
"$(jq -r '.total_cost_usd // "?" | if type=="number" then (.*10000|round)/10000 else . end' "$j")" \
50+
"$(grep -q 'SOLUTION' "$RUN_DIR/$v.answer.md" && echo yes || echo NO)"
51+
}
52+
53+
echo
54+
echo "==================== COMPARISON ===================="
55+
report bare
56+
report skilled
57+
echo "====================================================="
58+
echo
59+
echo "Full answers:"
60+
echo " $RUN_DIR/bare.answer.md"
61+
echo " $RUN_DIR/skilled.answer.md"
62+
echo
63+
echo "Compare them side by side, e.g.:"
64+
echo " diff -y --width 160 $RUN_DIR/bare.answer.md $RUN_DIR/skilled.answer.md"

test/validate_skills.sh

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/usr/bin/env bash
2+
# Static validation: every skill is well-formed and respects the
3+
# knowledge/skills separation rule documented in the repo README.
4+
set -u
5+
6+
REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)"
7+
SKILLS_DIR="$REPO_DIR/skills"
8+
errors=0
9+
10+
fail() { echo " FAIL: $1"; errors=$((errors + 1)); }
11+
12+
for skill_md in "$SKILLS_DIR"/*/SKILL.md; do
13+
dir_name="$(basename "$(dirname "$skill_md")")"
14+
echo "Checking $dir_name"
15+
16+
head -1 "$skill_md" | grep -q '^---$' || fail "file must start with '---' (YAML frontmatter)"
17+
18+
frontmatter="$(awk '/^---$/{n++; next} n==1{print} n>1{exit}' "$skill_md")"
19+
20+
name="$(echo "$frontmatter" | sed -n 's/^name:[[:space:]]*//p')"
21+
[ -n "$name" ] || fail "frontmatter is missing 'name'"
22+
[ "$name" = "$dir_name" ] || fail "frontmatter name '$name' does not match folder '$dir_name'"
23+
24+
description="$(echo "$frontmatter" | sed -n 's/^description:[[:space:]]*//p')"
25+
[ -n "$description" ] || fail "frontmatter is missing 'description'"
26+
[ "${#description}" -le 1024 ] || fail "description exceeds 1024 characters"
27+
28+
if grep -qn 'knowledge/' "$skill_md"; then
29+
fail "skill references knowledge/ — skills must be self-contained (see README)"
30+
fi
31+
32+
grep -q 'openapi-auth' "$skill_md" || [ "$dir_name" = "openapi-auth" ] \
33+
|| fail "skill does not mention the openapi-auth skill for authentication"
34+
done
35+
36+
echo
37+
if [ "$errors" -eq 0 ]; then
38+
echo "OK: all skills are valid."
39+
else
40+
echo "$errors problem(s) found."
41+
exit 1
42+
fi

0 commit comments

Comments
 (0)