Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
133 commits
Select commit Hold shift + click to select a range
29e2cb1
feat(ci): Add workflow to evaluate skills with discovery step
yongsinp Apr 19, 2026
4014ff8
fix(ci): Set ANTHROPIC_BASE_URL in evaluate-skills workflow
yongsinp Apr 19, 2026
d1654f5
feat(ci): Add evals for the scientific-documentation skill
yongsinp Apr 20, 2026
0101b9d
fix(ci): fix evaluate-skills workflow to discover nested skills
yongsinp Apr 20, 2026
27c0ab4
fix(ci): use correct GitHub secret for API key
yongsinp Apr 20, 2026
5c87e82
chore: ignore .idea directory
yongsinp Apr 20, 2026
ede3727
fix(ci): loosen RTD dependency criterion to accept any valid config s…
yongsinp Apr 20, 2026
8b9006e
fix(ci): use basename for skill name to avoid slashes in viewer filename
yongsinp Apr 20, 2026
6704305
feat(ci): Add evals for the code-quality-tools skill
yongsinp Apr 20, 2026
03c8ee8
feat(ci): Add evals for the pixi-package-manager skill
yongsinp Apr 20, 2026
72e774a
docs(skills): refine skill description for clarity
yongsinp Apr 20, 2026
dda4b46
Merge remote-tracking branch 'upstream/main'
yongsinp Apr 20, 2026
7594145
feat(ci): add Tessl skill review
yongsinp Apr 21, 2026
a37c837
refactor(ci): rename evaluate-skills.yml to evaluate-all-skills.yml
yongsinp Apr 21, 2026
7f94e39
fix(ci): review all skills, only evaluate skills with evals/
yongsinp Apr 21, 2026
bd3cee1
fix(evals): rephrase ruff prompt to avoid file creation action
yongsinp Apr 21, 2026
da3cc81
fix(evals): loosen numpy type hint criterion
yongsinp Apr 21, 2026
2084996
fix(evals): loosen pre-commit criterion to accept description or example
yongsinp Apr 21, 2026
3bfe742
feat(ci): add tessl skill review output to Actions step summary
yongsinp Apr 21, 2026
5b5c105
feat(ci): include collapsible judge evaluation in skill review summary
yongsinp Apr 21, 2026
14975c3
fix(ci): move status emoji into skill review heading
yongsinp Apr 21, 2026
b98dbbe
fix(ci): add threshold (80) for skill review
yongsinp Apr 21, 2026
a7925dd
fix(ci): add separate pass/fail status to validation checks summary h…
yongsinp Apr 21, 2026
9e6c44f
fix(ci): always write step summary before propagating review exit code
yongsinp Apr 21, 2026
facb796
feat(ci): fetch all available models and run evaluation for each model
yongsinp Apr 27, 2026
feadb97
fix(ci): point skill-eval-action to forked repository for testing
yongsinp Apr 27, 2026
7c87e7c
fix(ci): hardcode test models instead of fetching from LiteLLM proxy
yongsinp Apr 27, 2026
0f0f33f
refactor(ci): move skill and model name to top
yongsinp Apr 27, 2026
3c2eec0
feat(ci): add cross-model summary
yongsinp Apr 27, 2026
9c5e95c
refactor(ci): split review and evaluate into separate jobs and consol…
yongsinp Apr 27, 2026
7ff1233
chore(ci): bump artifact actions to node 24 compatible versions
yongsinp Apr 27, 2026
ce4f1c4
feat(ci): suppress redundant per-job eval summaries
yongsinp Apr 27, 2026
a4edb05
style(ci): reorganize summary layout with eval results table and comp…
yongsinp Apr 27, 2026
f8be2e6
feat(ci): add token usage to eval results summary table
yongsinp Apr 27, 2026
90eed4e
style(ci): add chart emoji to eval results section header
yongsinp Apr 27, 2026
6375201
fix(ci): add pass/fail emoji to eval results table rows
yongsinp Apr 27, 2026
6e82c39
refactor(ci): define pass threshold once at workflow level
yongsinp Apr 27, 2026
127f427
fix(evaluate-all-skills): read eval outputs via step context instead …
yongsinp Apr 27, 2026
6eb04d2
feat(evaluate-all-skills): filter evaluate matrix to skills with eval…
yongsinp Apr 27, 2026
8fb3142
style(ci): rename tokens column to "Tokens Used" in eval summary
yongsinp Apr 27, 2026
4a7cbfa
feat(ci): add baseline mode to measure value-add of the skills
yongsinp Apr 27, 2026
3b54bbc
style(ci): use pp (percentage points) for the delta column
yongsinp Apr 27, 2026
106b6ab
style(ci): replace empty table with plain message when no evals exist
yongsinp Apr 27, 2026
52e95fb
feat(ci): re-add per-case breakdown table under each model in summary
yongsinp Apr 27, 2026
c633445
feat(ci): add baseline per-case breakdown table under each model heading
yongsinp Apr 27, 2026
f966a7c
style(ci): remove emoji from per-model heading
yongsinp Apr 27, 2026
4bca717
perf(ci): batch review jobs into chunks of 8 to reduce runner overhead
yongsinp Apr 27, 2026
80ddfee
Revert "perf(ci): batch review jobs into chunks of 8 to reduce runner…
yongsinp Apr 27, 2026
502be98
fix(ci): resolve Claude Code and LiteLLM compatibility
yongsinp May 2, 2026
79ce0e1
refactor(ci): extract bash summary logic into Python script
yongsinp May 2, 2026
242a337
style(ci): make per-model eval detail sections collapsible
yongsinp May 2, 2026
fc0d672
style(ci): use title-case summary headers
yongsinp May 2, 2026
e981674
style(ci): swap baseline and skill column order in eval results table
yongsinp May 2, 2026
af8e6ae
feat: replace claude CLI with copilot CLI
yongsinp May 2, 2026
30ed880
Revert "fix(ci): hardcode test models instead of fetching from LiteLL…
yongsinp May 2, 2026
d277177
chore: remove all evals directories
yongsinp May 2, 2026
c2199f0
feat(ci): pull testcases from an external private repository
yongsinp May 2, 2026
0caa335
refactor(ci): extract per-model evaluation into reusable workflow
yongsinp May 5, 2026
235aa76
fix(ci): hardcode reusable workflow inputs
yongsinp May 5, 2026
e1b9392
feat(ci): add configurable sleep before evaluation to avoid rate limits
yongsinp May 18, 2026
40c066a
ci: change sleep duration to 5 mins
yongsinp May 22, 2026
4708b0e
feat(ci): generate testcases PR when new skill is added
yongsinp May 26, 2026
68032e0
Merge branch 'uw-ssec:main' into main
yongsinp May 26, 2026
be72dab
fix(ci): fix hardcoded testcases repo to match evaluate-all-skills pa…
yongsinp May 26, 2026
acd024b
Merge remote-tracking branch 'origin/main'
yongsinp May 26, 2026
d0873d3
fix(ci): fix syntax error in `generate-testcases.yaml`
yongsinp May 26, 2026
12c75c2
docs(ci): update comments
yongsinp May 26, 2026
9ccf81c
fix(ci): fix hardcoded testcases repo
yongsinp May 26, 2026
0aaf162
fix(ci): fix prompt for yaml parsing errors and naming consistency
yongsinp May 26, 2026
a431552
fix(ci): surface YAML validation warnings in testcases PR body
yongsinp May 26, 2026
a8e576b
fix(ci): fix hardcoded testcases repo
yongsinp May 29, 2026
bede417
refactor(ci): move sleep to after evaluation
yongsinp May 31, 2026
2caff2a
refactor: temporarily remove sleep for testing
yongsinp May 31, 2026
f51b281
docs(download-script-dev): update skill based on judge feedback
yongsinp Jun 1, 2026
2bc7d27
feat: format skill name as 'plugin :: skill' in the results
yongsinp Jun 1, 2026
ad95646
docs(research-workflow-management): update skill based on judge feedback
yongsinp Jun 1, 2026
b54c376
docs(compose-orchestration): update skill based on judge feedback
yongsinp Jun 1, 2026
0974a35
docs(container-registries): update skill based on judge feedback
yongsinp Jun 1, 2026
9e8a2ec
docs(container-security): update skill based on judge feedback
yongsinp Jun 1, 2026
2eb02e0
docs(devcontainers): update skill based on judge feedback
yongsinp Jun 1, 2026
d873436
docs(dockerfile-patterns): update skill based on judge feedback
yongsinp Jun 1, 2026
3ea5b8b
docs(gpu-containers): update skill based on judge feedback
yongsinp Jun 1, 2026
9691346
docs(podman): update skill based on judge feedback
yongsinp Jun 1, 2026
761fd59
docs(singularity-apptainer): update skill based on judge feedback
yongsinp Jun 1, 2026
22212c5
Merge remote-tracking branch 'upstream/main'
yongsinp Jun 1, 2026
7f68823
docs(community-health-files): update skill based on judge feedback
yongsinp Jun 2, 2026
d993cb0
docs(documentation-validation): update skill based on judge feedback
yongsinp Jun 2, 2026
51994d0
docs(user-persona-discovery): update skill based on judge feedback
yongsinp Jun 2, 2026
adbdcaf
docs(astropy-fundamentals): update skill based on judge feedback
yongsinp Jun 2, 2026
14cce31
docs(xarray-for-multidimensional-data): update skill based on judge f…
yongsinp Jun 2, 2026
d1d0691
docs(code-quality-tools): update skill based on judge feedback
yongsinp Jun 2, 2026
072f4db
docs(pixi-package-manager): update skill based on judge feedback
yongsinp Jun 2, 2026
9ebb342
docs(python-testing): update skill based on judge feedback
yongsinp Jun 2, 2026
e458204
docs(scientific-documentation): update skill based on judge feedback
yongsinp Jun 2, 2026
5442cb1
docs(ab-testing-strategy): update skill based on judge feedback
yongsinp Jun 2, 2026
5b81158
docs(accessibility-audit): update skill based on judge feedback
yongsinp Jun 2, 2026
487e498
refactor(ci): further limit parallelism to reduce rate limit pressure
yongsinp Jun 2, 2026
04148af
docs(color-systems): update skill based on judge feedback
yongsinp Jun 2, 2026
e429b8a
fix(generate-testcases): fix default timeout for automatically genera…
yongsinp Jun 2, 2026
724c5d6
fix(generate-testcases): require inline content for workspace files
yongsinp Jun 2, 2026
7c7f12c
docs(component-library): update skill based on judge feedback
yongsinp Jun 2, 2026
9cc151f
docs(css-architecture): update skill based on judge feedback
yongsinp Jun 2, 2026
8177456
docs(design-case-studies): update skill based on judge feedback
yongsinp Jun 2, 2026
fea03b5
docs(design-handoff): update skill based on judge feedback
yongsinp Jun 2, 2026
74896f8
docs(design-philosophies): update skill based on judge feedback
yongsinp Jun 2, 2026
c672555
docs(design-system-creation): update skill based on judge feedback
yongsinp Jun 2, 2026
3e2b186
docs(design-tokens): update skill based on judge feedback
yongsinp Jun 2, 2026
34f6a3d
docs(frontend-components): update skill based on judge feedback
yongsinp Jun 2, 2026
33cff51
docs(grid-layout-systems): update skill based on judge feedback
yongsinp Jun 2, 2026
299eb6d
docs(information-architecture): update skill based on judge feedback
yongsinp Jun 2, 2026
b083ddd
docs(motion-design): update skill based on judge feedback
yongsinp Jun 2, 2026
9cefe6a
docs(responsive-design): update skill based on judge feedback
yongsinp Jun 2, 2026
b0b52ea
docs(typography-systems): update skill based on judge feedback
yongsinp Jun 2, 2026
756180b
docs(usability-evaluation): update skill based on judge feedback
yongsinp Jun 2, 2026
f0dd0d1
docs(user-journey-mapping): update skill based on judge feedback
yongsinp Jun 2, 2026
7ad8330
docs(user-research): update skill based on judge feedback
yongsinp Jun 2, 2026
e1f3611
docs(ux-writing): update skill based on judge feedback
yongsinp Jun 2, 2026
9302b68
docs(visual-design): update skill based on judge feedback
yongsinp Jun 2, 2026
ed0d3d7
docs(wireframing): update skill based on judge feedback
yongsinp Jun 2, 2026
c72ad0f
docs(access-pattern-analysis): update skill based on judge feedback
yongsinp Jun 2, 2026
e4649fe
docs(chunking-strategy): update skill based on judge feedback
yongsinp Jun 2, 2026
4d14dfb
docs(performance-reporting): update skill based on judge feedback
yongsinp Jun 2, 2026
b3fac5e
docs(rechunking): update skill based on judge feedback
yongsinp Jun 2, 2026
f08cbae
docs(synthetic-data): update skill based on judge feedback
yongsinp Jun 2, 2026
747d21b
Revert "refactor: temporarily remove sleep for testing"
yongsinp Jun 2, 2026
535f155
docs(cloud-storage-backends): update skill based on judge feedback
yongsinp Jun 2, 2026
c915462
docs(compression-codecs): update skill based on judge feedback
yongsinp Jun 2, 2026
acd42ac
docs(data-migration): update skill based on judge feedback
yongsinp Jun 2, 2026
a896e43
docs(zarr-fundamentals): update skill based on judge feedback
yongsinp Jun 2, 2026
9f073ef
docs(zarr-xarray-integration): update skill based on judge feedback
yongsinp Jun 2, 2026
d24031e
docs: second pass of updating skills based on judge feedback
yongsinp Jun 2, 2026
b22e3e9
fix(skills): quote frontmatter descriptions containing colons
yongsinp Jun 2, 2026
322459b
docs(user-persona-discovery): second pass of updating skills based on…
yongsinp Jun 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 176 additions & 0 deletions .github/scripts/generate-testcases.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
#!/usr/bin/env python3
"""Generate eval testcases for newly added skills using the LiteLLM proxy."""

import argparse
import json
import os
import re
import sys
from pathlib import Path

import yaml
from openai import OpenAI

SYSTEM_PROMPT = """\
You generate evaluation test cases for AI coding-assistant skills.

Each skill has a SKILL.md that defines when and how it should be invoked.
Your job is to generate realistic eval cases that test whether the skill
triggers correctly and produces quality output.

## Eval YAML format

Always use a YAML block scalar (`|`) for the `prompt` field — prompts often contain
colons, JSON, or quotes that break inline YAML values.

```yaml
name: <Short human-readable name>
prompt: |
<Realistic user message that would trigger (or not) the skill>
criteria:
- <Specific, verifiable assertion about the response>
- <Another specific assertion>
expect_skill: true # false for the negative-trigger case
timeout: 600 # use 600 for all cases including the negative-trigger case
```

## Rules

- Generate **5–6 cases total**: 4–5 positive (`expect_skill: true`), exactly 1 negative (`expect_skill: false`).
- Positive cases must cover distinct use cases mentioned in the skill.
- Criteria must be **specific and verifiable** by a separate judge LLM.
Good: "Uses @pytest.mark.parametrize decorator"
Bad: "Output follows best practices"
- If a test case needs a file in the workspace, always include it as a `files:` entry with inline `content:` — never reference an external file path without content, as it will not exist at eval time.
- Prompts must sound like a real developer wrote them, not like a test case.
- The negative-trigger prompt should be plausibly related but clearly outside scope.
- Files must be sequentially numbered starting at 001. The last file must be the negative-trigger case, named `NNN-negative-trigger.yaml`.

## Output format

Return a JSON array only — no markdown fences, no explanation:

[
{
"filename": "001-kebab-case-name.yaml",
"content": "name: ...\\nprompt: ...\\ncriteria:\\n - ...\\nexpect_skill: true\\ntimeout: 600\\n"
},
...
]
"""

USER_PROMPT = """\
Skill path: {skill_path}

SKILL.md content:
{skill_content}

Generate 5–6 evaluation test cases for this skill.
"""


def extract_json_array(text: str) -> list:
text = text.strip()
if text.startswith("["):
return json.loads(text)
match = re.search(r"\[[\s\S]*\]", text)
if match:
return json.loads(match.group())
raise ValueError(f"No JSON array found in response:\n{text[:500]}")


def validate_testcase(content: str, filename: str) -> None:
data = yaml.safe_load(content)
if not isinstance(data, dict):
raise ValueError(f"{filename}: content did not parse as a YAML mapping")
required = {"name", "prompt", "expect_skill"}
missing = required - set(data.keys())
if missing:
raise ValueError(f"{filename}: missing required fields: {missing}")
if "criteria" not in data and "grading" not in data:
raise ValueError(f"{filename}: must have 'criteria' or 'grading'")


def generate_for_skill(
skill_path: str,
skill_root: Path,
client: OpenAI,
model: str,
) -> list[dict]:
skill_content = (skill_root / skill_path).read_text()
response = client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": USER_PROMPT.format(
skill_path=skill_path,
skill_content=skill_content,
)},
],
temperature=0.7,
max_tokens=4096,
)
return extract_json_array(response.choices[0].message.content)


def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--skills", required=True, help="JSON array of SKILL.md paths")
parser.add_argument("--skill-root", required=True, help="Root dir containing skill files")
parser.add_argument("--output-dir", required=True, help="Output root for generated evals")
parser.add_argument("--model", default="claude-sonnet-4-6")
args = parser.parse_args()

skills: list[str] = json.loads(args.skills)
if not skills:
print("No skills to process")
return

client = OpenAI(
api_key=os.environ["LITELLM_API_KEY"],
base_url=os.environ["LITELLM_PROXY_URL"].rstrip("/") + "/v1",
)

skill_root = Path(args.skill_root)
output_root = Path(args.output_dir)
errors: list[tuple[str, str]] = []
warnings: list[str] = []

for skill_path in skills:
print(f"\nGenerating testcases for: {skill_path}")
evals_dir = output_root / Path(skill_path).parent / "evals"

try:
testcases = generate_for_skill(skill_path, skill_root, client, args.model)
except Exception as exc:
print(f" ERROR: {exc}", file=sys.stderr)
errors.append((skill_path, str(exc)))
continue

evals_dir.mkdir(parents=True, exist_ok=True)
for tc in testcases:
filename: str = tc["filename"]
content: str = tc["content"]
try:
validate_testcase(content, filename)
except Exception as exc:
msg = f"{filename}: {exc}"
print(f" WARNING {msg} (written anyway)", file=sys.stderr)
warnings.append(msg)
out_file = evals_dir / filename
out_file.write_text(content)
print(f" Written: {out_file}")

if warnings:
warnings_file = output_root / ".validation-warnings"
warnings_file.write_text("\n".join(warnings) + "\n")

if errors:
print(f"\n{len(errors)} skill(s) failed generation:", file=sys.stderr)
for path, msg in errors:
print(f" {path}: {msg}", file=sys.stderr)
sys.exit(1)


if __name__ == "__main__":
main()
Loading
Loading