Skip to content

Commit ad1493d

Browse files
yannrichetclaude
andcommitted
add skills/test_skill_e2e.md: shell-only version of the skill e2e test
Same probe / sandbox / activation / end-to-end study / artifact checks as tests/test_skill_e2e.py, as explained copy-pasteable shell blocks (claude headless + jq assertions). Validated by extracting the bash blocks verbatim and running them: all checks pass with haiku. Includes the < /dev/null stdin redirect on claude calls, without which headless claude swallows the rest of a piped script. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent b4f42ba commit ad1493d

2 files changed

Lines changed: 155 additions & 1 deletion

File tree

skills/howto.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ to results.json" \
9797

9898
`tests/test_skill_e2e.py` in the fz repository does exactly this against a miniature
9999
simulation and asserts the produced results are physically correct — read it for a
100-
complete, working reference.
100+
complete, working reference, or follow [test_skill_e2e.md](test_skill_e2e.md) for the
101+
same test as explained, copy-pasteable shell commands.
101102

102103
## What's in the skill
103104

skills/test_skill_e2e.md

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# Testing the fz skill with shell commands only
2+
3+
This is the shell-only equivalent of `tests/test_skill_e2e.py`: it drives a headless
4+
Claude Code session against the fz skill and a tiny deterministic simulation (perfect gas
5+
pressure), then asserts on the **artifacts** the agent leaves behind — never on its prose.
6+
Paste the blocks in order, or save them as a script. Every block ends in a checkable exit
7+
status, so `bash -e` (or `&&`-chaining) aborts at the first failure. The `< /dev/null` on each
8+
`claude` call matters when running this as a script: headless claude reads piped stdin as
9+
extra input and would otherwise swallow the rest of the script.
10+
11+
Requirements: `claude` CLI (logged in, or `ANTHROPIC_API_KEY` set), `fz` on PATH
12+
(`pip install funz-fz`), `jq`.
13+
14+
```bash
15+
# Model used by the agent session — haiku keeps the cost negligible
16+
MODEL=${FZ_SKILL_TEST_MODEL:-claude-haiku-4-5}
17+
```
18+
19+
## 0. Probe: does claude actually work here?
20+
21+
One tiny one-turn call. If this fails (not installed, not logged in, no key), there is no
22+
point running the rest — this is exactly the check the pytest version uses to self-skip.
23+
24+
```bash
25+
claude -p "Reply with exactly: OK" --model "$MODEL" --max-turns 1 < /dev/null \
26+
|| { echo "claude CLI not functional — stopping"; exit 1; }
27+
```
28+
29+
## 1. Build a sandbox project
30+
31+
The sandbox must live **outside any git repository** (in particular outside the fz repo):
32+
Claude Code resolves its project at the enclosing repo root, and would otherwise ignore
33+
the sandbox's `.claude/skills/`.
34+
35+
```bash
36+
FZ_REPO=~/Sync/Open/Funz/github/fz # adjust to your fz checkout
37+
SANDBOX=$(mktemp -d)
38+
cd "$SANDBOX"
39+
40+
# the skill, project-level
41+
mkdir -p .claude/skills
42+
cp -r "$FZ_REPO/skills/fz" .claude/skills/
43+
```
44+
45+
The simulation: an input file parameterized with fz syntax (`$var` variables, `@{...}`
46+
formulas), a solver script, and a model alias telling fz how to parse the output.
47+
48+
```bash
49+
# input file: 3 variables, 2 compile-time formulas
50+
cat > input.txt <<'EOF'
51+
# perfect gas pressure input
52+
n_mol=$n_mol
53+
T_kelvin=@{$T_celsius + 273.15}
54+
V_m3=@{$V_L / 1000}
55+
EOF
56+
57+
# the "simulation": computes P = nRT/V into output.txt
58+
cat > calc.sh <<'EOF'
59+
#!/bin/bash
60+
source $1
61+
python3 -c "print('pressure =', $n_mol*8.314*$T_kelvin/$V_m3)" > output.txt
62+
EOF
63+
64+
# model alias: variable syntax + how to extract the pressure
65+
mkdir -p .fz/models
66+
cat > .fz/models/perfectgas.json <<'EOF'
67+
{
68+
"varprefix": "$",
69+
"formulaprefix": "@",
70+
"delim": "{}",
71+
"commentline": "#",
72+
"output": {"pressure": "grep 'pressure = ' output.txt | awk '{print $3}'"},
73+
"id": "perfectgas"
74+
}
75+
EOF
76+
```
77+
78+
## 2. Level 1 — skill activation
79+
80+
Ask a question whose answer requires the skill, and check the transcript shows the skill
81+
was actually loaded. `--output-format stream-json --verbose` makes every tool call visible
82+
as a JSON line; `--allowedTools` pre-authorizes the tools so the headless session never
83+
blocks on a permission prompt.
84+
85+
```bash
86+
claude -p "Using the fz skill, find which input variables input.txt contains
87+
(the fz model alias is 'perfectgas') and list their names." \
88+
--output-format stream-json --verbose \
89+
--max-turns 10 --model "$MODEL" \
90+
--allowedTools "Bash,Read,Write,Edit,Glob,Grep,Skill" < /dev/null > transcript.jsonl
91+
92+
# evidence the skill was used: a Skill tool call on "fz", or SKILL.md being read
93+
grep -q -e '"Skill"' -e 'SKILL.md' transcript.jsonl && echo "PASS: skill used"
94+
95+
# the three variables must appear in the transcript (final answer)
96+
for var in n_mol T_celsius V_L; do
97+
grep -q "$var" transcript.jsonl || { echo "FAIL: $var not found"; exit 1; }
98+
done
99+
echo "PASS: all variables identified"
100+
```
101+
102+
## 3. Level 2 — end-to-end parametric study
103+
104+
Give the agent the full task and let it choose its path; we only check the artifact it
105+
must produce. 2×2 = 4 cases, physics known exactly, so the assertions are deterministic
106+
even though the agent's reasoning is not.
107+
108+
```bash
109+
claude -p "Using the fz skill, run a parametric study of the simulation launched by
110+
'bash calc.sh' (fz model alias: 'perfectgas', input file: input.txt) over
111+
T_celsius in [10, 20] and V_L in [1, 2] with n_mol fixed to 1.
112+
Then write the results to a file named results.json as a JSON list of
113+
records, one per case, each with keys T_celsius, V_L, n_mol, pressure, status." \
114+
--output-format stream-json --verbose \
115+
--max-turns 40 --model "$MODEL" \
116+
--allowedTools "Bash,Read,Write,Edit,Glob,Grep,Skill" < /dev/null > transcript2.jsonl
117+
```
118+
119+
Now the artifact checks — the same three as the pytest version:
120+
121+
```bash
122+
# 1. the file exists and is valid JSON with 4 case records, all done
123+
jq -e 'length == 4 and all(.[]; .status == "done")' results.json \
124+
&& echo "PASS: 4 cases, all done"
125+
126+
# 2. every pressure matches P = nRT/V within 0.1%
127+
jq -e '
128+
all(.[];
129+
(8.314 * (.T_celsius + 273.15) / (.V_L / 1000)) as $expected
130+
| ((.pressure - $expected) / $expected | fabs) < 0.001
131+
)' results.json && echo "PASS: pressures physically correct"
132+
```
133+
134+
(No `jq`? The same check in python:
135+
`python3 -c "import json; rs=json.load(open('results.json')); assert len(rs)==4 and all(r['status']=='done' and abs(r['pressure']-8.314*(r['T_celsius']+273.15)/(r['V_L']/1000))/ (8.314*(r['T_celsius']+273.15)/(r['V_L']/1000)) < 1e-3 for r in rs); print('PASS')"`)
136+
137+
## 4. Clean up
138+
139+
```bash
140+
cd / && rm -rf "$SANDBOX"
141+
```
142+
143+
## Notes
144+
145+
- **Assert on artifacts, not prose**: the agent's wording varies between runs; the
146+
existence and numerical content of `results.json` (and the case directories fz creates
147+
under `results/`) do not.
148+
- Expect ~10 s for Level 1 and ~40 s for Level 2 with haiku; a few cents of tokens.
149+
- If Level 2 fails, look inside the sandbox before deleting it: `results/*/out.txt`,
150+
`err.txt` and `log.txt` show what each fz case actually did, and `transcript2.jsonl`
151+
shows what the agent tried.
152+
- The pytest version (`tests/test_skill_e2e.py`) automates exactly this, probe included:
153+
`venv/bin/python -m pytest tests/test_skill_e2e.py -v`.

0 commit comments

Comments
 (0)