Skip to content

Commit 9908827

Browse files
danbarrjhrozekclaude
authored
Add test-docs skill for validating documentation (#520)
* Add test-docs skill for validating documentation Adds a Claude Code skill that executes tutorial and guide steps against a live environment to verify documentation accuracy. Supports both CLI and Kubernetes-based docs. Co-Authored-By: Jakub Hrozek <jhrozek@users.noreply.github.com> Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Exclude .claude/ from linters Skill files are prompts for Claude Code, not documentation, so they shouldn't be subject to the same markdown/formatting rules. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Address Copilot review feedback - Add SPDX license headers to check-prereqs.sh - Change shebang to #!/bin/bash for consistency - Fix cluster name references to lowercase "toolhive" - Escape asterisks in placeholder patterns - Make kind/cluster check a warning instead of error (optional for CLI docs) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Dan Barr <6922515+danbarr@users.noreply.github.com> Co-authored-by: Jakub Hrozek <jhrozek@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 7523937 commit 9908827

4 files changed

Lines changed: 326 additions & 0 deletions

File tree

.claude/skills/test-docs/SKILL.md

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
---
2+
name: test-docs
3+
description: >
4+
Test ToolHive documentation by executing the steps in tutorials and guides
5+
against a real environment, verifying that examples are correct and ToolHive
6+
has not regressed. Use when the user asks to test, validate, or verify
7+
documentation such as: "test the vault integration tutorial",
8+
"verify the K8s quickstart", "check the CLI install docs",
9+
"test toolhive vault integration in kubernetes", or any request to run
10+
through a doc's instructions to confirm they work. Supports both
11+
Kubernetes-based docs (tutorials, K8s guides) and CLI-based docs.
12+
---
13+
14+
# Test Docs
15+
16+
Test ToolHive documentation by running each step against a live environment,
17+
reporting pass/fail with evidence, and recommending fixes for failures.
18+
19+
## Workflow
20+
21+
1. **Find the documentation** - launch an Explore agent to locate the
22+
relevant doc file
23+
2. **Read and parse** - extract testable steps (bash code blocks, YAML
24+
manifests, expected outputs)
25+
3. **Check prerequisites** - run `scripts/check-prereqs.sh`; confirm the
26+
`thv` version with the user
27+
4. **Prepare the environment** - create a dedicated test namespace unless
28+
the doc specifies otherwise
29+
5. **Execute steps** - run each step sequentially, capture output
30+
6. **Report results** - pass/fail per step with evidence
31+
7. **Clean up** - delete all resources created during testing
32+
8. **Recommend fixes** - classify failures as doc issues or ToolHive bugs
33+
34+
## Step 1: Find the documentation
35+
36+
If the user provides a **file path** to the doc, use it directly. Assume
37+
you are running in the repository that contains the file.
38+
39+
Otherwise, use the Task tool with `subagent_type=Explore` to search the
40+
docs directory for the page matching the user's request. Documentation
41+
lives under:
42+
43+
- `docs/toolhive/tutorials/` - step-by-step tutorials
44+
- `docs/toolhive/guides-cli/` - CLI how-to guides
45+
- `docs/toolhive/guides-k8s/` - Kubernetes how-to guides
46+
- `docs/toolhive/guides-mcp/` - MCP server guides
47+
- `docs/toolhive/guides-vmcp/` - Virtual MCP Server guides
48+
- `docs/toolhive/guides-registry/` - registry guides
49+
50+
All doc files use the `.mdx` extension. Search by keywords from the user's
51+
request. If multiple docs match, ask the user which one to test.
52+
53+
## Step 2: Read and parse the doc
54+
55+
Read the full doc file. Extract an ordered list of **testable steps**:
56+
57+
- **Bash code blocks** (`\`\`\`bash`) - commands to execute
58+
- **YAML code blocks** with `title="*.yaml"` - manifests to apply with
59+
`kubectl apply`
60+
- **Expected outputs** - text code blocks (`\`\`\`text`) that follow a
61+
command, or prose describing expected behavior ("You should see...")
62+
63+
Build a test plan: a numbered list of steps, each with:
64+
65+
- The command(s) to run
66+
- What constitutes a pass (expected output, exit code 0, resource created)
67+
- Any dependencies on previous steps (variables, resources)
68+
69+
Present the test plan to the user for approval before executing.
70+
71+
### Handling placeholders
72+
73+
Docs often contain placeholder values like `ghp_your_github_token_here` or
74+
`your-org`. Before executing, scan for obvious placeholders (ALL_CAPS
75+
patterns, "your-\*", "example-\*", "replace-\*") and ask the user for real
76+
values. If a step requires a secret that the user cannot provide, mark it
77+
as **skipped** with a note explaining why.
78+
79+
## Step 3: Check prerequisites
80+
81+
Run the prerequisites checker:
82+
83+
```bash
84+
bash <skill-path>/scripts/check-prereqs.sh
85+
```
86+
87+
Where `<skill-path>` is the absolute path to this skill's directory.
88+
89+
If the script reports errors, inform the user and stop. For K8s docs, the
90+
script checks for a kind cluster named "toolhive". For CLI docs, confirm
91+
with the user that the installed `thv` version matches what the doc expects.
92+
93+
## Step 4: Prepare the environment
94+
95+
For Kubernetes docs, ask the user about existing infrastructure before
96+
deploying anything:
97+
98+
- **Operator CRDs and operator**: if the doc includes steps to install
99+
CRDs or the ToolHive operator, ask the user whether these are already
100+
installed. If so, skip those installation steps and proceed to the
101+
doc-specific content. Use `AskUserQuestion` with options like
102+
"Already installed", "Install fresh", "Reinstall (upgrade)".
103+
- **Namespaces**: create a dedicated namespace `test-docs-<timestamp>`
104+
(e.g., `test-docs-1706886400`) unless the doc explicitly uses a
105+
specific namespace (like `toolhive-system`). If the doc requires
106+
`toolhive-system`, use it but track all resources created for cleanup.
107+
108+
For CLI docs:
109+
110+
- Use a temporary directory for any files created
111+
- Note the state of running MCP servers before testing (`thv list`)
112+
- **Use unique server names**: when running `thv run <server>`, always use
113+
`--name <server>-test` to avoid conflicts with existing servers. The
114+
default name is derived from the registry entry, which may already be
115+
running. Example: `thv run --name osv-test osv`
116+
- **Verify registry configuration**: if a registry server isn't found,
117+
confirm with the user that they're using the default registry (not a
118+
custom registry configuration)
119+
120+
## Step 5: Execute steps
121+
122+
Run each step sequentially. For each step:
123+
124+
1. Print the step number and a short description
125+
2. Run the command(s)
126+
3. Capture stdout, stderr, and exit code
127+
4. Compare against expected output or success criteria
128+
5. Record pass/fail with evidence
129+
130+
### Execution rules
131+
132+
- **Wait for readiness**: when a step deploys resources, wait for them
133+
(e.g., `kubectl wait --for=condition=ready`) before proceeding. If the
134+
doc includes a wait command, use it. Otherwise add a reasonable wait
135+
(up to 120s for pods).
136+
- **Variable propagation**: some steps set shell variables used by later
137+
steps (e.g., `VAULT_POD=$(kubectl get pods ...)`). Maintain these
138+
across steps by running in the same shell session.
139+
- **Skip non-testable steps**: informational code blocks (showing file
140+
contents, expected output) are not commands to run. Use them as
141+
verification targets instead.
142+
- **Timeout**: if any command hangs for more than 5 minutes, kill it and
143+
mark the step as failed with a timeout note.
144+
145+
### Known pitfalls
146+
147+
- **Server name conflicts**: `thv run <server>` uses the registry entry
148+
name by default, which may already exist. Always use
149+
`thv run --name <server>-test <server>` to create a uniquely-named
150+
instance. This avoids "workload with name already exists" errors.
151+
- **Port-forward needs time**: when testing via `kubectl port-forward`,
152+
wait at least 5 seconds before issuing `curl`. Start the port-forward
153+
in the background, sleep 5, then curl. Kill the port-forward PID
154+
after the check.
155+
- **MCPServer status field**: the MCPServer CRD uses `.status.phase`
156+
(not `.status.state`) for the running state. When polling for
157+
readiness, use:
158+
`kubectl get mcpservers <name> -n <ns> -o jsonpath='{.status.phase}'`
159+
or simply poll `kubectl get mcpservers` and check the STATUS column.
160+
- **MCPServer readiness polling**: after `kubectl apply` for an
161+
MCPServer, it may take 30-60 seconds to reach `Running`. Poll with
162+
5-second intervals up to 120 seconds before declaring a timeout.
163+
164+
## Step 6: Report results
165+
166+
After all steps complete, produce a summary table:
167+
168+
```text
169+
## Test Results: <doc title>
170+
171+
| Step | Description | Result | Notes |
172+
|------|----------------------|--------|--------------------------|
173+
| 1 | Install Vault | PASS | |
174+
| 2 | Configure auth | PASS | |
175+
| 3 | Store secrets | PASS | |
176+
| 4 | Deploy MCPServer | FAIL | Pod CrashLoopBackOff |
177+
| 5 | Verify integration | SKIP | Depends on step 4 |
178+
```
179+
180+
For each **FAIL**, include:
181+
182+
- The command that failed
183+
- Actual output vs expected output
184+
- A classification:
185+
- **Doc issue**: the command or example in the doc is wrong or outdated
186+
(e.g., wrong image tag, deprecated flag, missing step)
187+
- **ToolHive bug**: the doc appears correct but ToolHive behaves
188+
unexpectedly (e.g., crash, wrong output from a correct command)
189+
- **Environment issue**: missing prerequisite, network problem, or
190+
transient error
191+
- A specific recommendation (e.g., "Update image tag from `v0.7.0` to
192+
`v0.8.2`" or "File a bug: MCPServer pods fail to start when Vault
193+
annotations are present")
194+
195+
## Step 7: Clean up
196+
197+
Always clean up after testing:
198+
199+
- Delete test namespaces: `kubectl delete namespace <test-namespace>`
200+
- Remove Helm releases installed during testing
201+
- Delete any temporary files or directories
202+
- If resources were created in `toolhive-system`, delete them individually
203+
by name
204+
205+
If cleanup fails, report what could not be cleaned up so the user can
206+
handle it manually.
207+
208+
## Example session
209+
210+
User: "test the vault integration tutorial"
211+
212+
1. Explore agent finds `docs/toolhive/tutorials/vault-integration.mdx`
213+
2. Parse the doc: 5 major steps with 12 bash commands and 1 YAML manifest
214+
3. Run `check-prereqs.sh` - kind cluster "toolhive" exists, thv v0.8.2
215+
4. Ask user: "The doc uses a placeholder GitHub token
216+
`ghp_your_github_token_here`. Provide a real token or skip step 2?"
217+
5. Present test plan, user approves
218+
6. Execute steps 1-5, report results
219+
7. Clean up: delete vault namespace, remove MCPServer resource
220+
8. Summary: 4/5 PASS, 1 FAIL with recommendation
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#!/bin/bash
2+
# SPDX-FileCopyrightText: Copyright 2026 Stacklok, Inc.
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
# Check prerequisites for ToolHive documentation testing.
6+
# Exits with non-zero status if critical prerequisites are missing.
7+
# Outputs a summary of what's available.
8+
9+
set -euo pipefail
10+
11+
RED='\033[0;31m'
12+
GREEN='\033[0;32m'
13+
YELLOW='\033[0;33m'
14+
NC='\033[0m' # No Color
15+
16+
ERRORS=0
17+
WARNINGS=0
18+
19+
check_command() {
20+
local cmd="$1"
21+
local required="$2" # "required" or "optional"
22+
if command -v "$cmd" &>/dev/null; then
23+
local version
24+
version=$("$cmd" version 2>/dev/null | head -1 || "$cmd" --version 2>/dev/null | head -1 || echo "unknown")
25+
echo -e "${GREEN}[OK]${NC} $cmd: $version"
26+
elif [ "$required" = "required" ]; then
27+
echo -e "${RED}[MISSING]${NC} $cmd (required)"
28+
ERRORS=$((ERRORS + 1))
29+
else
30+
echo -e "${YELLOW}[MISSING]${NC} $cmd (optional)"
31+
WARNINGS=$((WARNINGS + 1))
32+
fi
33+
}
34+
35+
check_kind_cluster() {
36+
local cluster_name="${1:-toolhive}"
37+
if ! command -v kind &>/dev/null; then
38+
echo -e "${YELLOW}[SKIP]${NC} kind CLI not found (optional for CLI-only docs)"
39+
WARNINGS=$((WARNINGS + 1))
40+
return
41+
fi
42+
43+
if kind get clusters 2>/dev/null | grep -q "^${cluster_name}$"; then
44+
echo -e "${GREEN}[OK]${NC} kind cluster '$cluster_name' exists"
45+
46+
# Check if kubectl context is set to this cluster
47+
local current_context
48+
current_context=$(kubectl config current-context 2>/dev/null || echo "none")
49+
local expected_context="kind-${cluster_name}"
50+
if [ "$current_context" = "$expected_context" ]; then
51+
echo -e "${GREEN}[OK]${NC} kubectl context set to '$expected_context'"
52+
else
53+
echo -e "${YELLOW}[WARN]${NC} kubectl context is '$current_context', expected '$expected_context'"
54+
WARNINGS=$((WARNINGS + 1))
55+
fi
56+
else
57+
echo -e "${YELLOW}[WARN]${NC} kind cluster '$cluster_name' not found (required for K8s docs)"
58+
echo " Available clusters: $(kind get clusters 2>/dev/null | tr '\n' ' ')"
59+
WARNINGS=$((WARNINGS + 1))
60+
fi
61+
}
62+
63+
check_thv_version() {
64+
if ! command -v thv &>/dev/null; then
65+
echo -e "${RED}[MISSING]${NC} thv CLI not found"
66+
ERRORS=$((ERRORS + 1))
67+
return
68+
fi
69+
70+
local version
71+
version=$(thv version 2>/dev/null || echo "unknown")
72+
echo -e "${GREEN}[OK]${NC} thv: $version"
73+
}
74+
75+
echo "=== ToolHive Documentation Test Prerequisites ==="
76+
echo ""
77+
78+
echo "--- Required tools ---"
79+
check_command kubectl required
80+
check_thv_version
81+
check_command helm required
82+
83+
echo ""
84+
echo "--- Optional tools ---"
85+
check_command kind optional
86+
check_command jq optional
87+
check_command curl optional
88+
89+
echo ""
90+
echo "--- Kubernetes cluster ---"
91+
check_kind_cluster "${1:-toolhive}"
92+
93+
echo ""
94+
echo "=== Summary ==="
95+
if [ "$ERRORS" -gt 0 ]; then
96+
echo -e "${RED}$ERRORS error(s)${NC}, $WARNINGS warning(s)"
97+
echo "Fix the errors above before running documentation tests."
98+
exit 1
99+
else
100+
echo -e "${GREEN}All prerequisites met${NC} ($WARNINGS warning(s))"
101+
exit 0
102+
fi

.markdownlint-cli2.jsonc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"gitignore": true,
33
"globs": ["**/*.md"],
44
"ignores": [
5+
".claude/",
56
"docs/toolhive/reference/cli/",
67
"static/api-specs/*.md",
78
"docs/toolhive/reference/crd-spec.md",

.prettierignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,8 @@ docs/toolhive/reference/cli/**/*.md
33
static/api-specs/
44
docs/toolhive/reference/crd-spec.md
55

6+
# Ignore Claude Code skills (prompts, not documentation)
7+
.claude/
8+
69
# Ignore templates
710
.github/pull_request_template.md

0 commit comments

Comments
 (0)