skill-scoreboard 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- skill_scoreboard-0.1.0/.github/workflows/ci.yml +34 -0
- skill_scoreboard-0.1.0/.github/workflows/publish.yml +102 -0
- skill_scoreboard-0.1.0/.gitignore +13 -0
- skill_scoreboard-0.1.0/LICENSE +21 -0
- skill_scoreboard-0.1.0/PKG-INFO +361 -0
- skill_scoreboard-0.1.0/README.md +334 -0
- skill_scoreboard-0.1.0/examples/probes.yaml +35 -0
- skill_scoreboard-0.1.0/examples/skills/fastapi-schema/SKILL.md +50 -0
- skill_scoreboard-0.1.0/examples/skills/helpful-helper/SKILL.md +11 -0
- skill_scoreboard-0.1.0/examples/skills/py-validation/SKILL.md +31 -0
- skill_scoreboard-0.1.0/examples/tasks/pydev-fastapi-pydantic-01.yaml +41 -0
- skill_scoreboard-0.1.0/examples/tasks/pydev-hello-01.yaml +23 -0
- skill_scoreboard-0.1.0/pyproject.toml +79 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/__init__.py +3 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/ab.py +93 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/cli.py +477 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/config.py +55 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/evaluators/__init__.py +31 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/evaluators/ast_assertions.py +142 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/evaluators/base.py +48 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/evaluators/llm_judge.py +80 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/evaluators/pydev_static.py +98 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/evaluators/pydev_tests.py +62 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/evaluators/skill_activation.py +20 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/evaluators/trajectory_efficiency.py +54 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/llm.py +110 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/metrics/__init__.py +1 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/metrics/loops.py +153 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/metrics/stats.py +29 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/metrics/tokens.py +59 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/models/__init__.py +91 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/models/portfolio.py +143 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/models/results.py +98 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/models/task.py +187 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/models/trajectory.py +105 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/portfolio/__init__.py +31 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/portfolio/critic.py +76 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/portfolio/lint.py +138 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/portfolio/mine.py +84 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/portfolio/scorecard.py +264 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/portfolio/skills.py +80 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/portfolio/tfidf.py +114 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/portfolio/triggers.py +251 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/report.py +177 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/runner/__init__.py +8 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/runner/base.py +28 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/runner/claude_cli.py +117 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/runner/ndjson.py +210 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/runner/replay.py +50 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/runner/workspace.py +132 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/runs.py +69 -0
- skill_scoreboard-0.1.0/src/skill_scoreboard/trial.py +112 -0
- skill_scoreboard-0.1.0/tests/conftest.py +123 -0
- skill_scoreboard-0.1.0/tests/fixtures/make_fixtures.py +331 -0
- skill_scoreboard-0.1.0/tests/fixtures/ndjson/error_max_turns.ndjson +5 -0
- skill_scoreboard-0.1.0/tests/fixtures/ndjson/loopy_no_skill.ndjson +15 -0
- skill_scoreboard-0.1.0/tests/fixtures/ndjson/success_with_skill.ndjson +9 -0
- skill_scoreboard-0.1.0/tests/fixtures/sessions/mined_session.jsonl +5 -0
- skill_scoreboard-0.1.0/tests/test_ab.py +162 -0
- skill_scoreboard-0.1.0/tests/test_cli.py +253 -0
- skill_scoreboard-0.1.0/tests/test_evaluators.py +208 -0
- skill_scoreboard-0.1.0/tests/test_live.py +70 -0
- skill_scoreboard-0.1.0/tests/test_metrics.py +141 -0
- skill_scoreboard-0.1.0/tests/test_ndjson.py +83 -0
- skill_scoreboard-0.1.0/tests/test_portfolio.py +350 -0
- skill_scoreboard-0.1.0/tests/test_runners.py +107 -0
- skill_scoreboard-0.1.0/tests/test_task_schema.py +127 -0
- skill_scoreboard-0.1.0/uv.lock +794 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
check:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
|
|
14
|
+
- name: Install uv
|
|
15
|
+
uses: astral-sh/setup-uv@v5
|
|
16
|
+
|
|
17
|
+
# A uv-managed venv, not --system: the runner's system interpreter is
|
|
18
|
+
# externally managed (PEP 668) and refuses the install outright.
|
|
19
|
+
- name: Install project
|
|
20
|
+
run: |
|
|
21
|
+
uv python install 3.11
|
|
22
|
+
uv venv --python 3.11
|
|
23
|
+
uv pip install -e ".[dev]"
|
|
24
|
+
|
|
25
|
+
- name: Lint
|
|
26
|
+
run: uv run ruff check src tests
|
|
27
|
+
|
|
28
|
+
- name: Type check
|
|
29
|
+
run: uv run mypy src
|
|
30
|
+
|
|
31
|
+
# Fully offline: replay fixtures stand in for the CLI, and live-marked
|
|
32
|
+
# tests are deselected by default. No API key or network required.
|
|
33
|
+
- name: Test
|
|
34
|
+
run: uv run pytest -q
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
# Publishing is a one-way door: a version number can never be reused on PyPI,
|
|
4
|
+
# even after deletion. So every artifact is linted, type-checked, tested, and
|
|
5
|
+
# metadata-validated before upload, and a tag whose version disagrees with
|
|
6
|
+
# pyproject.toml fails the build instead of shipping the wrong number.
|
|
7
|
+
#
|
|
8
|
+
# Auth is PyPI Trusted Publishing (OIDC) — no API token is stored in this
|
|
9
|
+
# repository. Configure the publisher once per index at
|
|
10
|
+
# https://pypi.org/manage/account/publishing/ with:
|
|
11
|
+
# owner/repository: 700799/skill-scoreboard
|
|
12
|
+
# workflow name: publish.yml
|
|
13
|
+
# environment: pypi (and testpypi on test.pypi.org)
|
|
14
|
+
|
|
15
|
+
on:
|
|
16
|
+
push:
|
|
17
|
+
tags: ["v*"]
|
|
18
|
+
workflow_dispatch:
|
|
19
|
+
inputs:
|
|
20
|
+
target:
|
|
21
|
+
description: "Index to publish to"
|
|
22
|
+
type: choice
|
|
23
|
+
options: [testpypi, pypi]
|
|
24
|
+
default: testpypi
|
|
25
|
+
|
|
26
|
+
jobs:
|
|
27
|
+
build:
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@v4
|
|
31
|
+
|
|
32
|
+
- name: Install uv
|
|
33
|
+
uses: astral-sh/setup-uv@v5
|
|
34
|
+
|
|
35
|
+
- name: Install project
|
|
36
|
+
run: |
|
|
37
|
+
uv python install 3.11
|
|
38
|
+
uv venv --python 3.11
|
|
39
|
+
uv pip install -e ".[dev]"
|
|
40
|
+
|
|
41
|
+
- name: Verify tag matches project version
|
|
42
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
43
|
+
run: |
|
|
44
|
+
project=$(uv run python -c \
|
|
45
|
+
"import tomllib, pathlib; print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version'])")
|
|
46
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
47
|
+
echo "pyproject: $project / tag: $tag"
|
|
48
|
+
if [ "$project" != "$tag" ]; then
|
|
49
|
+
echo "::error::tag $GITHUB_REF_NAME does not match pyproject version $project"
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
- name: Lint
|
|
54
|
+
run: uv run ruff check src tests
|
|
55
|
+
|
|
56
|
+
- name: Type check
|
|
57
|
+
run: uv run mypy src
|
|
58
|
+
|
|
59
|
+
- name: Test
|
|
60
|
+
run: uv run pytest -q
|
|
61
|
+
|
|
62
|
+
- name: Build
|
|
63
|
+
run: uv build
|
|
64
|
+
|
|
65
|
+
- name: Validate metadata
|
|
66
|
+
run: uvx twine check dist/*
|
|
67
|
+
|
|
68
|
+
- uses: actions/upload-artifact@v4
|
|
69
|
+
with:
|
|
70
|
+
name: dist
|
|
71
|
+
path: dist/
|
|
72
|
+
if-no-files-found: error
|
|
73
|
+
|
|
74
|
+
publish-testpypi:
|
|
75
|
+
needs: build
|
|
76
|
+
if: github.event_name == 'workflow_dispatch' && inputs.target == 'testpypi'
|
|
77
|
+
runs-on: ubuntu-latest
|
|
78
|
+
environment: testpypi
|
|
79
|
+
permissions:
|
|
80
|
+
id-token: write
|
|
81
|
+
steps:
|
|
82
|
+
- uses: actions/download-artifact@v4
|
|
83
|
+
with:
|
|
84
|
+
name: dist
|
|
85
|
+
path: dist
|
|
86
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
87
|
+
with:
|
|
88
|
+
repository-url: https://test.pypi.org/legacy/
|
|
89
|
+
|
|
90
|
+
publish-pypi:
|
|
91
|
+
needs: build
|
|
92
|
+
if: github.event_name == 'push' || inputs.target == 'pypi'
|
|
93
|
+
runs-on: ubuntu-latest
|
|
94
|
+
environment: pypi
|
|
95
|
+
permissions:
|
|
96
|
+
id-token: write
|
|
97
|
+
steps:
|
|
98
|
+
- uses: actions/download-artifact@v4
|
|
99
|
+
with:
|
|
100
|
+
name: dist
|
|
101
|
+
path: dist
|
|
102
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 skill-scoreboard contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: skill-scoreboard
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Benchmark Claude Code skills against a no-skill baseline, detect token waste and skill bypass, and rank a whole skill library KEEP/FIX/MERGE/RETIRE.
|
|
5
|
+
Project-URL: Repository, https://github.com/700799/skill-scoreboard
|
|
6
|
+
Author: skill-scoreboard contributors
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: agent,benchmark,claude,claude-code,evaluation,llm,skills
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
16
|
+
Classifier: Topic :: Software Development :: Testing
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Requires-Dist: anthropic>=0.40
|
|
19
|
+
Requires-Dist: pydantic>=2.0
|
|
20
|
+
Requires-Dist: pyyaml>=6.0
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
24
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
25
|
+
Requires-Dist: types-pyyaml; extra == 'dev'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# skill-scoreboard
|
|
29
|
+
|
|
30
|
+
**Measure whether your Claude Code skills actually earn their keep — then rank your whole library.**
|
|
31
|
+
|
|
32
|
+
A skill is just instructions in a Markdown file. That makes it easy to write and
|
|
33
|
+
impossible to evaluate by reading: a capable model often completes the task with
|
|
34
|
+
or without it. So "the tests passed" proves nothing about the skill, and a library
|
|
35
|
+
grows until nobody can say which entries help, which overlap, and which are quietly
|
|
36
|
+
costing tokens on every activation.
|
|
37
|
+
|
|
38
|
+
`skill-scoreboard` answers that with measurement instead of opinion. Every task runs
|
|
39
|
+
twice — **control** with skills disabled, **treatment** with the skill injected — and
|
|
40
|
+
the difference is the skill's value:
|
|
41
|
+
|
|
42
|
+
| metric | control | treatment | delta |
|
|
43
|
+
|---|---|---|---|
|
|
44
|
+
| pass rate | 0% | 100% | +100pp |
|
|
45
|
+
| mean total tokens | 65,400 | 33,575 | -48.7% |
|
|
46
|
+
| duplicate tool calls | 3.0 | 0.0 | -3.0 |
|
|
47
|
+
| file re-reads | 2.0 | 0.0 | -2.0 |
|
|
48
|
+
| skill activation | 0% | 100% | +100pp |
|
|
49
|
+
|
|
50
|
+
Point it at a whole directory of skills and it returns a ranked scoreboard with a
|
|
51
|
+
decision and a stated reason for each one:
|
|
52
|
+
|
|
53
|
+
| rank | skill | score | confidence | recommendation | reasons |
|
|
54
|
+
|---|---|---|---|---|---|
|
|
55
|
+
| 1 | `fastapi-schema` | 1.00 | 55% | **KEEP** | clears every tier that has run |
|
|
56
|
+
| 2 | `py-validation` | 1.00 | 15% | **MERGE → fastapi-schema** | overlaps fastapi-schema (similarity 0.55); fastapi-schema scores higher |
|
|
57
|
+
| 3 | `helpful-helper` | 0.43 | 15% | **FIX** | blocking lint findings: dead-reference; composite 0.43 is mid-range |
|
|
58
|
+
|
|
59
|
+
Both tables above are real output from the shipped examples, not mockups.
|
|
60
|
+
|
|
61
|
+
### What makes it more than a test runner
|
|
62
|
+
|
|
63
|
+
- **It catches the bypass.** A skill whose tasks pass while its *activation rate*
|
|
64
|
+
stays low isn't working — the model is solving the problem from prior knowledge.
|
|
65
|
+
That pattern gets flagged `FIX` instead of counted as success.
|
|
66
|
+
- **It measures waste, not just correctness.** Duplicate tool calls, re-reading
|
|
67
|
+
large files without editing them, and runaway per-turn context growth are
|
|
68
|
+
detected from the agent's trajectory.
|
|
69
|
+
- **It checks adherence structurally.** Python AST assertions verify the skill's
|
|
70
|
+
prescribed constructs are present and its forbidden ones absent — so "used
|
|
71
|
+
Pydantic v2 `@field_validator`, never the v1 `@validator`" is a test, not a hope.
|
|
72
|
+
- **It never fakes confidence.** Every score carries the share of evidence behind
|
|
73
|
+
it. A skill with only a free static lint cannot outrank one measured end to end,
|
|
74
|
+
and is never retired on thin evidence.
|
|
75
|
+
- **It runs offline.** Recorded sessions replay deterministically, so the whole
|
|
76
|
+
suite — including the real `ruff`, `mypy`, and `pytest` evaluators — runs in CI
|
|
77
|
+
with no API key.
|
|
78
|
+
|
|
79
|
+
### Two ways to use it
|
|
80
|
+
|
|
81
|
+
| | Command | Cost |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| **One skill, deeply** | `skill-scoreboard ab task.yaml` | Real agent runs |
|
|
84
|
+
| **A whole library** | `skill-scoreboard audit ./skills` → `rank` | Free static tier first, paid tiers only where they earn it |
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Install
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pip install skill-scoreboard # installs the `skill-scoreboard` command
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
From a checkout, for development:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
uv venv && uv pip install -e ".[dev]"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Requires Python 3.11+. Live runs need the [Claude Code CLI](https://code.claude.com/docs)
|
|
101
|
+
on `PATH`; the LLM judge and critic need API credentials. **Everything else runs
|
|
102
|
+
offline** — pass `--no-llm` and `--runner replay` to work with no network at all.
|
|
103
|
+
|
|
104
|
+
## Quickstart
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
skill-scoreboard validate examples/tasks/*.yaml examples/probes.yaml # schema-check
|
|
108
|
+
skill-scoreboard --no-llm audit examples/skills --json runs/audit.json # Tier 0: lint + overlap
|
|
109
|
+
skill-scoreboard mine ~/.claude/projects --skills-dir examples/skills --json runs/mine.json
|
|
110
|
+
skill-scoreboard --no-llm ab examples/tasks/pydev-fastapi-pydantic-01.yaml \
|
|
111
|
+
--runner replay --trials 2 \
|
|
112
|
+
--control-fixture tests/fixtures/ndjson/loopy_no_skill.ndjson \
|
|
113
|
+
--treatment-fixture tests/fixtures/ndjson/success_with_skill.ndjson
|
|
114
|
+
skill-scoreboard rank examples/skills --audit runs/audit.json --runs runs \
|
|
115
|
+
--mine runs/mine.json --md runs/leaderboard.md
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Drop `--runner replay` and its fixtures to run against the real CLI.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## The four measurements
|
|
123
|
+
|
|
124
|
+
### 1. Task success & code quality
|
|
125
|
+
|
|
126
|
+
`pydev_static` runs **ruff** and **mypy** over the final workspace; `pydev_tests`
|
|
127
|
+
runs **pytest**; `ast_assertions` parses the resulting code with Python's own
|
|
128
|
+
**AST** module. Missing tools degrade to a recorded failure rather than a crash.
|
|
129
|
+
|
|
130
|
+
### 2. Token waste & loop detection
|
|
131
|
+
|
|
132
|
+
Three anti-patterns, computed from the trajectory:
|
|
133
|
+
|
|
134
|
+
| Detector | What counts | Why it is not naive |
|
|
135
|
+
|---|---|---|
|
|
136
|
+
| Duplicate tool calls | Same tool + byte-identical input | Canonical JSON hashing; exempt list for legitimately repeated tools (`TodoWrite`) |
|
|
137
|
+
| Large-file re-reads | Re-reading a >100-line file | An `Edit`/`Write` to that file **resets** the window — re-reading your own change is legitimate |
|
|
138
|
+
| Context growth | Tokens added per turn (default limit 15k) | Exact when the stream carries per-message usage; falls back to a chars/4 estimate and **flags that it did** |
|
|
139
|
+
|
|
140
|
+
### 3. Skill adherence vs. generic bypass
|
|
141
|
+
|
|
142
|
+
The framework's sharpest signal. Three independent checks:
|
|
143
|
+
|
|
144
|
+
1. **`skill_activation`** — did the `Skill` tool actually fire for the target skill?
|
|
145
|
+
2. **`ast_assertions`** — are the skill's prescribed constructs present and its
|
|
146
|
+
forbidden ones absent (e.g. Pydantic v2 `@field_validator` required,
|
|
147
|
+
v1 `@validator` banned)?
|
|
148
|
+
3. **`llm_judge`** — a rubric graded by Claude for what static analysis can't see.
|
|
149
|
+
|
|
150
|
+
A skill whose tasks pass while activation stays low is flagged **FIX**: the model
|
|
151
|
+
is solving it from prior knowledge and the skill is along for the ride.
|
|
152
|
+
|
|
153
|
+
### 4. A/B baseline differential
|
|
154
|
+
|
|
155
|
+
Every task runs in two arms:
|
|
156
|
+
|
|
157
|
+
| Arm | How | Meaning |
|
|
158
|
+
|---|---|---|
|
|
159
|
+
| **Control** | `claude -p --disable-slash-commands` | What the model does unaided |
|
|
160
|
+
| **Treatment** | Skill injected at `.claude/skills/<name>/SKILL.md` | What the skill adds |
|
|
161
|
+
|
|
162
|
+
Reports give `treatment − control` deltas for pass rate, tokens, cost, duration,
|
|
163
|
+
and each waste metric. Negative token/cost deltas mean the skill *saves*.
|
|
164
|
+
|
|
165
|
+
> The control arm uses `--disable-slash-commands`, **not** `--bare`. `--bare`
|
|
166
|
+
> only skips hooks, LSP, and plugin credentials — its help notes that "Skills
|
|
167
|
+
> still resolve via `/skill-name`", so a `--bare` baseline would silently load
|
|
168
|
+
> the very skill under test and understate its measured value.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Ranking a whole library
|
|
173
|
+
|
|
174
|
+
Cost scales with how deeply you want to probe, so a large library can be triaged
|
|
175
|
+
before spending real money:
|
|
176
|
+
|
|
177
|
+
| Tier | Command | Cost | Produces |
|
|
178
|
+
|---|---|---|---|
|
|
179
|
+
| **0 · Static** | `skill-scoreboard audit <dir>` | free | Lint findings + TF-IDF overlap clusters |
|
|
180
|
+
| **1 · Triggers** | `skill-scoreboard audit <dir> --probes probes.yaml` | cheap | Precision/recall/F1 + cross-activation confusion matrix |
|
|
181
|
+
| **2 · A/B** | `skill-scoreboard ab <task.yaml>` | real runs | Measured value lift |
|
|
182
|
+
| **Critic** | `skill-scoreboard audit <dir> --critic` | one call/skill | Actionable rewrite suggestions |
|
|
183
|
+
| **Mining** | `skill-scoreboard mine <transcripts>` | free | Real-world activation frequency and cost |
|
|
184
|
+
|
|
185
|
+
**Tier 0** catches the problems you can find for free: descriptions too vague to
|
|
186
|
+
route on, skills whose own body costs thousands of tokens on every activation,
|
|
187
|
+
dead file references, and near-duplicate skills that should be merged.
|
|
188
|
+
|
|
189
|
+
**Tier 1** asks whether a skill fires when it should — and stays quiet when it
|
|
190
|
+
shouldn't. Each skill's positive probes double as negatives for every *other*
|
|
191
|
+
skill, which fills a confusion matrix and exposes skills that keep firing on
|
|
192
|
+
each other's work (a merge signal independent of text similarity).
|
|
193
|
+
|
|
194
|
+
### Scoring
|
|
195
|
+
|
|
196
|
+
Composite score is a weighted mean over **whichever tiers have run**, with
|
|
197
|
+
weights renormalized across what's available — so a Tier 0-only library still
|
|
198
|
+
ranks, and the score sharpens as evidence accumulates.
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
lint 0.15 · trigger 0.25 · ab 0.40 · critic 0.20 (override with --weights)
|
|
202
|
+
|
|
203
|
+
ab = 0.50 × pass-rate lift + 0.25 × token savings + 0.25 × adherence
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Recommendations, in precedence order:
|
|
207
|
+
|
|
208
|
+
- **RETIRE** — measured with no pass-rate lift, no token savings, and weak
|
|
209
|
+
triggering; or a poor composite backed by real evidence; or never activated
|
|
210
|
+
in mined sessions while scoring badly.
|
|
211
|
+
- **MERGE** — overlaps a higher-scoring sibling (TF-IDF cluster or
|
|
212
|
+
cross-activation). `merge_with` names the survivor.
|
|
213
|
+
- **FIX** — blocking lint errors, trigger F1 below 0.70, a mid-range composite,
|
|
214
|
+
or the bypass signature above.
|
|
215
|
+
- **KEEP** — clears every tier that has run.
|
|
216
|
+
|
|
217
|
+
**Confidence and ranking.** Renormalizing over available tiers alone would let a
|
|
218
|
+
skill with nothing but clean lint tie — or beat — one measured end to end and
|
|
219
|
+
found good. So each entry also carries a **confidence** (the share of scoring
|
|
220
|
+
weight actually backed by evidence), and *ranking* uses the composite shrunk
|
|
221
|
+
toward a neutral 0.5 in proportion to what's missing:
|
|
222
|
+
|
|
223
|
+
```
|
|
224
|
+
ranked = composite × confidence + 0.5 × (1 − confidence)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
An unmeasured skill therefore cannot outrank a measured one, and the survivor of
|
|
228
|
+
a merge cluster is the best-*evidenced* member rather than the luckiest. The raw
|
|
229
|
+
composite is still what threshold decisions read, so the two roles stay separate.
|
|
230
|
+
|
|
231
|
+
**Guardrail:** a skill is never retired on static lint alone. With Tier 0 only,
|
|
232
|
+
the worst outcome is FIX, reason `insufficient_evidence`, and the leaderboard
|
|
233
|
+
prints `—` for every tier that didn't run. Thin evidence never impersonates a
|
|
234
|
+
measurement.
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Task schema
|
|
239
|
+
|
|
240
|
+
```yaml
|
|
241
|
+
id: pydev-fastapi-pydantic-01
|
|
242
|
+
description: "Verify the fastapi-schema skill refactors raw dicts into Pydantic v2 models."
|
|
243
|
+
target_skill: "../skills/fastapi-schema/SKILL.md"
|
|
244
|
+
|
|
245
|
+
workspace:
|
|
246
|
+
initial_files:
|
|
247
|
+
app.py: |
|
|
248
|
+
from fastapi import FastAPI
|
|
249
|
+
app = FastAPI()
|
|
250
|
+
|
|
251
|
+
prompt: "Refactor app.py to return a Pydantic model."
|
|
252
|
+
|
|
253
|
+
run_limits:
|
|
254
|
+
max_turns: 6 # -> claude --max-turns
|
|
255
|
+
max_budget_usd: 0.15 # -> claude --max-budget-usd
|
|
256
|
+
# timeout_seconds, model, allowed_tools, disallowed_tools also supported
|
|
257
|
+
|
|
258
|
+
evaluations:
|
|
259
|
+
- type: skill_activation
|
|
260
|
+
expected: "fastapi-schema"
|
|
261
|
+
- type: pydev_static
|
|
262
|
+
linters: [ruff, mypy]
|
|
263
|
+
strict: true
|
|
264
|
+
- type: pydev_tests
|
|
265
|
+
- type: ast_assertions
|
|
266
|
+
assertions:
|
|
267
|
+
- {file: app.py, kind: decorator_used, value: field_validator}
|
|
268
|
+
- {file: app.py, kind: forbidden_decorator, value: validator}
|
|
269
|
+
- type: trajectory_efficiency
|
|
270
|
+
max_duplicate_tool_calls: 1
|
|
271
|
+
max_file_re_reads: 2
|
|
272
|
+
- type: llm_judge
|
|
273
|
+
rubric: "Did the code use Pydantic V2 `@field_validator` syntax?"
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
The schema is strict: unknown fields and unknown evaluation types fail at load
|
|
277
|
+
time, not mid-run. `ast_assertions` kinds: `decorator_used`, `call_used`,
|
|
278
|
+
`import_used`, `class_inherits`, `function_defined`, and the `forbidden_*`
|
|
279
|
+
variants.
|
|
280
|
+
|
|
281
|
+
`probes.yaml` (for Tier 1) is documented in `examples/probes.yaml`.
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## Architecture
|
|
286
|
+
|
|
287
|
+
Every measurement reads one normalized `Trajectory`, and three sources produce it:
|
|
288
|
+
|
|
289
|
+
```
|
|
290
|
+
claude -p --output-format stream-json ─┐
|
|
291
|
+
recorded NDJSON fixture ───────────────┼──> runner/ndjson.py ──> Trajectory ──> metrics
|
|
292
|
+
real session transcript (skill-scoreboard mine) ────┘ └──> evaluators
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Because replay is a first-class source, the entire pipeline — including the
|
|
296
|
+
ruff/mypy/pytest evaluators — is exercised in CI with no API key and no network.
|
|
297
|
+
`ReplayRunner` re-applies the `Write`/`Edit` calls recorded in a fixture, so an
|
|
298
|
+
offline run rebuilds the workspace the agent actually produced instead of
|
|
299
|
+
grading the starting files.
|
|
300
|
+
|
|
301
|
+
Cost comes from the CLI's reported `total_cost_usd` when present; otherwise it
|
|
302
|
+
is estimated from a pricing table and **marked as estimated** in every report.
|
|
303
|
+
|
|
304
|
+
## Development
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
uv run ruff check src tests
|
|
308
|
+
uv run mypy src
|
|
309
|
+
uv run pytest # offline; live-marked tests are deselected by default
|
|
310
|
+
uv run pytest -m live # needs an authenticated claude CLI, spends real tokens
|
|
311
|
+
python tests/fixtures/make_fixtures.py # regenerate NDJSON fixtures
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## Relationship to `claude plugin eval`
|
|
315
|
+
|
|
316
|
+
Claude Code ships an early-access, org-gated `claude plugin eval` that also runs
|
|
317
|
+
control/treatment arms with regex, tool-use, and LLM graders. This project is
|
|
318
|
+
standalone and complementary: it adds trajectory-level waste detection, AST
|
|
319
|
+
adherence checks, and portfolio-scale ranking, and it runs anywhere the CLI
|
|
320
|
+
runs. It targets the `2.1.x` stream-json shape (live-verified against CLI
|
|
321
|
+
2.1.246 and 2.1.296) and parses defensively,
|
|
322
|
+
ignoring unknown event types and fields so format drift degrades rather than breaks.
|
|
323
|
+
|
|
324
|
+
## Releasing
|
|
325
|
+
|
|
326
|
+
Publishing runs on [PyPI Trusted Publishing](https://docs.pypi.org/trusted-publishers/)
|
|
327
|
+
via `.github/workflows/publish.yml` — no API token is stored in this repository.
|
|
328
|
+
|
|
329
|
+
One-time setup, per index, at https://pypi.org/manage/account/publishing/
|
|
330
|
+
(and the same on test.pypi.org):
|
|
331
|
+
|
|
332
|
+
| Field | Value |
|
|
333
|
+
|---|---|
|
|
334
|
+
| PyPI project name | `skill-scoreboard` |
|
|
335
|
+
| Owner / repository | `700799` / `skill-scoreboard` |
|
|
336
|
+
| Workflow name | `publish.yml` |
|
|
337
|
+
| Environment | `pypi` (`testpypi` on TestPyPI) |
|
|
338
|
+
|
|
339
|
+
Then:
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
# Dry run to TestPyPI first — Actions tab -> Publish -> Run workflow -> testpypi
|
|
343
|
+
# Real release:
|
|
344
|
+
git tag v0.1.0 && git push origin v0.1.0
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
The workflow lints, type-checks, tests, builds, and runs `twine check` before
|
|
348
|
+
uploading, and **fails the build if the tag version disagrees with
|
|
349
|
+
`pyproject.toml`**. That guard matters: a version number can never be reused on
|
|
350
|
+
PyPI, even after deletion, so a wrong tag would otherwise burn it permanently.
|
|
351
|
+
|
|
352
|
+
To publish by hand with an API token instead (username is literally `__token__`):
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
uv build && uvx twine check dist/*
|
|
356
|
+
uvx twine upload dist/*
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## License
|
|
360
|
+
|
|
361
|
+
MIT
|