skill-evals 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.
Files changed (31) hide show
  1. skill_evals-0.1.0/LICENSE +21 -0
  2. skill_evals-0.1.0/PKG-INFO +20 -0
  3. skill_evals-0.1.0/pyproject.toml +116 -0
  4. skill_evals-0.1.0/pyproject.toml.orig +132 -0
  5. skill_evals-0.1.0/src/skill_evals/__init__.py +7 -0
  6. skill_evals-0.1.0/src/skill_evals/cli.py +162 -0
  7. skill_evals-0.1.0/src/skill_evals/config.py +494 -0
  8. skill_evals-0.1.0/src/skill_evals/harnesses/base.sbxenv.yaml +55 -0
  9. skill_evals-0.1.0/src/skill_evals/harnesses/claude/cli/spec.yaml +32 -0
  10. skill_evals-0.1.0/src/skill_evals/harnesses/claude/harness.yaml +24 -0
  11. skill_evals-0.1.0/src/skill_evals/harnesses/claude/sbxenv.yaml +16 -0
  12. skill_evals-0.1.0/src/skill_evals/harnesses/claude/tracing/spec.yaml +28 -0
  13. skill_evals-0.1.0/src/skill_evals/harnesses/codex/cli/spec.yaml +28 -0
  14. skill_evals-0.1.0/src/skill_evals/harnesses/codex/harness.yaml +17 -0
  15. skill_evals-0.1.0/src/skill_evals/harnesses/codex/sbxenv.yaml +16 -0
  16. skill_evals-0.1.0/src/skill_evals/harnesses/codex/tracing/files/home/.mlflow-codex/setup_tracing.py +123 -0
  17. skill_evals-0.1.0/src/skill_evals/harnesses/codex/tracing/spec.yaml +66 -0
  18. skill_evals-0.1.0/src/skill_evals/init.py +84 -0
  19. skill_evals-0.1.0/src/skill_evals/mlflow_server.py +140 -0
  20. skill_evals-0.1.0/src/skill_evals/run.py +481 -0
  21. skill_evals-0.1.0/src/skill_evals/schemas/config.schema.json +44 -0
  22. skill_evals-0.1.0/src/skill_evals/schemas/eval.schema.json +217 -0
  23. skill_evals-0.1.0/src/skill_evals/schemas/harness.schema.json +48 -0
  24. skill_evals-0.1.0/src/skill_evals/score.py +362 -0
  25. skill_evals-0.1.0/src/skill_evals/scorers/__init__.py +60 -0
  26. skill_evals-0.1.0/src/skill_evals/scorers/answers.py +37 -0
  27. skill_evals-0.1.0/src/skill_evals/scorers/skills.py +61 -0
  28. skill_evals-0.1.0/src/skill_evals/traces/__init__.py +49 -0
  29. skill_evals-0.1.0/src/skill_evals/traces/base.py +150 -0
  30. skill_evals-0.1.0/src/skill_evals/traces/claude.py +55 -0
  31. skill_evals-0.1.0/src/skill_evals/traces/codex.py +45 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Max van den Hoven
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,20 @@
1
+ Metadata-Version: 2.4
2
+ Name: skill-evals
3
+ Version: 0.1.0
4
+ Summary: Evaluate how coding-agent harnesses load and follow skills, in Docker sandboxes, traced with MLflow.
5
+ Author: Max van den Hoven
6
+ Author-email: Max van den Hoven <max.hoven@gmail.com>
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Topic :: Software Development :: Testing
16
+ Requires-Dist: jsonschema>=4.26.0
17
+ Requires-Dist: mlflow>=3.17
18
+ Requires-Dist: pandas>=3.0.6
19
+ Requires-Dist: pyyaml>=6
20
+ Requires-Python: >=3.12
@@ -0,0 +1,116 @@
1
+ [project]
2
+ name = "skill-evals"
3
+ version = "0.1.0"
4
+ description = "Evaluate how coding-agent harnesses load and follow skills, in Docker sandboxes, traced with MLflow."
5
+ license = "MIT"
6
+ license-files = ["LICENSE"]
7
+ classifiers = [
8
+ "Development Status :: 3 - Alpha",
9
+ "Environment :: Console",
10
+ "Intended Audience :: Developers",
11
+ "Programming Language :: Python :: 3",
12
+ "Programming Language :: Python :: 3.12",
13
+ "Programming Language :: Python :: 3.13",
14
+ "Topic :: Software Development :: Testing",
15
+ ]
16
+ requires-python = ">=3.12"
17
+ dependencies = [
18
+ "jsonschema>=4.26.0",
19
+ "mlflow>=3.17",
20
+ "pandas>=3.0.6",
21
+ "pyyaml>=6",
22
+ ]
23
+
24
+ [[project.authors]]
25
+ name = "Max van den Hoven"
26
+ email = "max.hoven@gmail.com"
27
+
28
+ [project.scripts]
29
+ skill-evals = "skill_evals.cli:main"
30
+
31
+ [build-system]
32
+ requires = ["uv_build>=0.11.2,<0.12.0"]
33
+ build-backend = "uv_build"
34
+
35
+ [dependency-groups]
36
+ dev = [
37
+ "commitizen>=4.19.2",
38
+ "pre-commit>=4.6.2",
39
+ "pytest>=9.1.1",
40
+ "pytest-cov>=7.1.0",
41
+ "ruff>=0.17.0",
42
+ "ty>=0.0.86",
43
+ "types-jsonschema>=4.26.0.20261006",
44
+ "yamllint>=1.38.0",
45
+ ]
46
+
47
+ [tool.pytest.ini_options]
48
+ testpaths = ["tests"]
49
+ addopts = "-m 'not integration'"
50
+ strict_markers = true
51
+ markers = ["integration: needs sbx and Docker login, no model credentials; not run by default"]
52
+
53
+ [tool.ty.environment]
54
+ python-version = "3.12"
55
+
56
+ [tool.ty.src]
57
+ include = [
58
+ "src/**",
59
+ "example/scorers/**",
60
+ "tests/**",
61
+ ]
62
+ exclude = ["example/fixtures/**"]
63
+
64
+ [tool.ruff]
65
+ line-length = 90
66
+ indent-width = 4
67
+ force-exclude = true
68
+ extend-exclude = [
69
+ ".runs",
70
+ ".mlflow",
71
+ "example/fixtures",
72
+ ]
73
+
74
+ [tool.ruff.lint]
75
+ preview = true
76
+ explicit-preview-rules = true
77
+ select = [
78
+ "B",
79
+ "D",
80
+ "E",
81
+ "F",
82
+ "I",
83
+ "EM",
84
+ "T20",
85
+ "RET",
86
+ "ANN",
87
+ "DOC201",
88
+ "DOC202",
89
+ "DOC402",
90
+ "DOC501",
91
+ "DOC502",
92
+ ]
93
+ ignore = [
94
+ "D100",
95
+ "D104",
96
+ "D203",
97
+ "D213",
98
+ "D400",
99
+ "D413",
100
+ "ANN002",
101
+ "ANN003",
102
+ "ANN401",
103
+ ]
104
+
105
+ [tool.ruff.lint.per-file-ignores]
106
+ "src/skill_evals/{cli,run,score,init,mlflow_server}.py" = ["T201"]
107
+ "tests/record_trace.py" = ["T201"]
108
+ "src/skill_evals/harnesses/codex/tracing/files/home/.mlflow-codex/setup_tracing.py" = ["T201"]
109
+
110
+ [tool.ruff.lint.pydocstyle]
111
+ convention = "google"
112
+
113
+ [tool.commitizen]
114
+ name = "cz_conventional_commits"
115
+ version_provider = "uv"
116
+ tag_format = "v$version"
@@ -0,0 +1,132 @@
1
+ # The `skill-evals` command (src/skill_evals/). It runs on the host; the only Python
2
+ # that runs inside sandboxes is the Codex tracing helper of the shipped harnesses
3
+ # (harnesses/codex/tracing/files/home/.mlflow-codex/setup_tracing.py). The example
4
+ # project in example/ isn't part of the package.
5
+ [project]
6
+ name = "skill-evals"
7
+ version = "0.1.0"
8
+ description = "Evaluate how coding-agent harnesses load and follow skills, in Docker sandboxes, traced with MLflow."
9
+ authors = [
10
+ { name = "Max van den Hoven", email = "max.hoven@gmail.com" },
11
+ ]
12
+ license = "MIT"
13
+ license-files = ["LICENSE"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Environment :: Console",
17
+ "Intended Audience :: Developers",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Programming Language :: Python :: 3.13",
21
+ "Topic :: Software Development :: Testing",
22
+ ]
23
+ requires-python = ">=3.12"
24
+ dependencies = [
25
+ "jsonschema>=4.26.0", # Validates project files against the packaged schemas
26
+ "mlflow>=3.17", # Tracking server, trace search and mlflow.genai.evaluate
27
+ "pandas>=3.0.6", # Trace tables from mlflow.search_traces
28
+ "pyyaml>=6", # Reads project files
29
+ ]
30
+
31
+ [project.scripts]
32
+ skill-evals = "skill_evals.cli:main"
33
+
34
+ [build-system]
35
+ requires = ["uv_build>=0.11.2,<0.12.0"]
36
+ build-backend = "uv_build"
37
+
38
+ [dependency-groups]
39
+ dev = [
40
+ "commitizen>=4.19.2",
41
+ "pre-commit>=4.6.2",
42
+ "pytest>=9.1.1",
43
+ "pytest-cov>=7.1.0",
44
+ "ruff>=0.17.0",
45
+ "ty>=0.0.86",
46
+ "types-jsonschema>=4.26.0.20261006",
47
+ "yamllint>=1.38.0",
48
+ ]
49
+
50
+ # Unit tests: no sbx, Docker, network, model credentials or MLflow server. Run them
51
+ # with `make test`, which adds a coverage report.
52
+ [tool.pytest.ini_options]
53
+ testpaths = ["tests"]
54
+ addopts = "-m 'not integration'"
55
+ strict_markers = true
56
+ markers = [
57
+ "integration: needs sbx and Docker login, no model credentials; not run by default",
58
+ ]
59
+
60
+ [tool.ty.environment]
61
+ python-version = "3.12" # The oldest supported version
62
+
63
+ [tool.ty.src]
64
+ include = [
65
+ "src/**",
66
+ "example/scorers/**",
67
+ "tests/**",
68
+ ]
69
+ # Fixture files are test material for the agents (e.g. deliberately without docstrings).
70
+ exclude = [
71
+ "example/fixtures/**",
72
+ ]
73
+
74
+ [tool.ruff]
75
+ line-length = 90
76
+ indent-width = 4
77
+ force-exclude = true # Also skip excluded files that pre-commit passes explicitly
78
+ extend-exclude = [
79
+ ".runs",
80
+ ".mlflow",
81
+ "example/fixtures", # Test material for the agents, not our code
82
+ ]
83
+
84
+ [tool.ruff.lint]
85
+ # The DOC (pydoclint) rules are preview-only; enable exactly those, nothing else in preview.
86
+ preview = true
87
+ explicit-preview-rules = true
88
+ select = [
89
+ "B", # flake8-bugbear
90
+ "D", # pydocstyle
91
+ "E", # pycodestyle
92
+ "F", # pyflakes
93
+ "I", # isort
94
+ "EM", # flake8-errmsg
95
+ "T20", # flake8-print
96
+ "RET", # flake8-return
97
+ "ANN", # flake8-annotations
98
+ "DOC201", # pydoclint: return value not documented in docstring
99
+ "DOC202", # pydoclint: docstring documents a return value the function lacks
100
+ "DOC402", # pydoclint: yield value not documented in docstring
101
+ "DOC501", # pydoclint: raised exception not documented in docstring
102
+ "DOC502", # pydoclint: docstring documents an exception that is not raised
103
+ ]
104
+ ignore = [
105
+ "D100", # Ignore missing docstring in public module
106
+ "D104", # Ignore missing docstring in public package
107
+ "D203", # Ignore one blank line before class docstring
108
+ "D213", # Ignore multi-line docstring start on second line
109
+ "D400", # Ignore missing period in docstring first line (also captured by D415)
110
+ "D413", # Ignore missing newline after final section in docstring
111
+ "ANN002", # Ignore missing *args type annotations
112
+ "ANN003", # Ignore missing **kwargs type annotations
113
+ "ANN401", # Ignore Any type annotations
114
+ ]
115
+
116
+ [tool.ruff.lint.per-file-ignores]
117
+ # Command-line code: printing is its output, not leftover debugging.
118
+ "src/skill_evals/{cli,run,score,init,mlflow_server}.py" = ["T201"]
119
+ "tests/record_trace.py" = ["T201"]
120
+ "src/skill_evals/harnesses/codex/tracing/files/home/.mlflow-codex/setup_tracing.py" = [
121
+ "T201",
122
+ ]
123
+
124
+ [tool.ruff.lint.pydocstyle]
125
+ convention = "google"
126
+
127
+ # Pull request titles follow Conventional Commits (feat:, fix:, docs:, ...), and squash
128
+ # merges make them the commit messages. `cz bump` reads them to pick the next version.
129
+ [tool.commitizen]
130
+ name = "cz_conventional_commits"
131
+ version_provider = "uv"
132
+ tag_format = "v$version"
@@ -0,0 +1,7 @@
1
+ """Evaluate whether coding agents load and follow skills, in Docker Sandboxes.
2
+
3
+ The `skill-evals` command (see cli.py) runs evals in sandboxes (run.py) and scores
4
+ their MLflow traces (score.py, with the scorers in scorers/ and the per-harness trace
5
+ readers in traces/). What it runs comes from the project that .skill-evals.yaml
6
+ describes (config.py); `skill-evals init` creates one with the shipped harnesses/.
7
+ """
@@ -0,0 +1,162 @@
1
+ """skill-evals: test whether coding agents load and follow your skills.
2
+
3
+ skill-evals init [DIR] Set up a project (.skill-evals.yaml + folders)
4
+ skill-evals evaluate Run the evals, then score them (run + score)
5
+ skill-evals run Run evals in sandboxes
6
+ skill-evals score Score runs' traces; check thresholds
7
+ skill-evals validate Check the project's eval and harness files
8
+ skill-evals mlflow Open the MLflow server (UI) on the project's store
9
+
10
+ run, score and evaluate start an MLflow server for their duration if none is up.
11
+ """
12
+
13
+ import argparse
14
+ import sys
15
+ from collections.abc import Callable
16
+ from pathlib import Path
17
+
18
+ from skill_evals import init, mlflow_server, run, score
19
+ from skill_evals.config import (
20
+ discover_evals,
21
+ discover_harnesses,
22
+ eval_cases,
23
+ load_harness,
24
+ use_config,
25
+ )
26
+
27
+ # A subcommand's function: takes the parsed options, returns an exit code (or, like
28
+ # `run`, something else that counts as success).
29
+ Handler = Callable[[argparse.Namespace], object]
30
+
31
+
32
+ def validate(args: argparse.Namespace) -> int:
33
+ """Validate every eval file and harness.yaml.
34
+
35
+ Args:
36
+ args: The parsed options; `validate` has none of its own.
37
+
38
+ Returns:
39
+ The exit code, 0 (invalid files stop with a message instead).
40
+ """
41
+ del args # Every subcommand takes the parsed options; this one has none
42
+ evals, harnesses = discover_evals(), discover_harnesses()
43
+ for name in evals:
44
+ eval_cases(name) # Validates the file and resolves fixtures and scorers
45
+ for name in harnesses:
46
+ load_harness(name)
47
+ print(f"valid: {len(evals)} eval(s), {len(harnesses)} harness(es)")
48
+ return 0
49
+
50
+
51
+ def evaluate(args: argparse.Namespace) -> int:
52
+ """Run the selected evals, then score exactly that run.
53
+
54
+ Args:
55
+ args: The parsed options of `run` plus the threshold options of `score`.
56
+
57
+ Returns:
58
+ The exit code of the scoring: 1 if a threshold was missed, else 0.
59
+ """
60
+ args.stamp = run.main(args)
61
+ return score.main(args)
62
+
63
+
64
+ def serve(args: argparse.Namespace) -> int:
65
+ """Run the MLflow server in the foreground.
66
+
67
+ Args:
68
+ args: The parsed options; `mlflow` has none of its own.
69
+
70
+ Returns:
71
+ The exit code, 0.
72
+ """
73
+ del args # Every subcommand takes the parsed options; this one has none
74
+ mlflow_server.serve()
75
+ return 0
76
+
77
+
78
+ def _with_mlflow(handler: Handler) -> Handler:
79
+ """Wrap a subcommand so an MLflow server runs while it does.
80
+
81
+ Args:
82
+ handler: The subcommand's function, taking the parsed options.
83
+
84
+ Returns:
85
+ A function with the same signature.
86
+ """
87
+
88
+ def wrapped(args: argparse.Namespace) -> int:
89
+ """Run the wrapped subcommand inside `mlflow_server.running`.
90
+
91
+ Args:
92
+ args: The parsed options.
93
+
94
+ Returns:
95
+ The subcommand's exit code; a non-integer result counts as 0.
96
+ """
97
+ with mlflow_server.running():
98
+ result = handler(args)
99
+ return result if isinstance(result, int) else 0
100
+
101
+ return wrapped
102
+
103
+
104
+ def build_parser() -> argparse.ArgumentParser:
105
+ """Build the command-line parser with every subcommand.
106
+
107
+ Returns:
108
+ The parser; each subcommand sets `handler`.
109
+ """
110
+ fmt = argparse.RawDescriptionHelpFormatter
111
+ ap = argparse.ArgumentParser(
112
+ prog="skill-evals", description=__doc__, formatter_class=fmt
113
+ )
114
+ ap.add_argument(
115
+ "--config",
116
+ type=Path,
117
+ help="the project's .skill-evals.yaml (default: the nearest one above the "
118
+ "current folder)",
119
+ )
120
+ sub = ap.add_subparsers(required=True, metavar="COMMAND")
121
+
122
+ p = sub.add_parser(
123
+ "init", help="set up a project", description=init.__doc__, formatter_class=fmt
124
+ )
125
+ p.add_argument(
126
+ "dir", nargs="?", default="skill-evals", help="folder (default: skill-evals)"
127
+ )
128
+ p.set_defaults(handler=init.main)
129
+
130
+ p = sub.add_parser(
131
+ "evaluate", help="run, then score that run", description=evaluate.__doc__
132
+ )
133
+ run.add_arguments(p)
134
+ score.add_threshold_arguments(p)
135
+ p.set_defaults(handler=_with_mlflow(evaluate))
136
+
137
+ p = sub.add_parser(
138
+ "run", help="run evals in sandboxes", description=run.__doc__, formatter_class=fmt
139
+ )
140
+ run.add_arguments(p)
141
+ p.set_defaults(handler=_with_mlflow(run.main))
142
+
143
+ p = sub.add_parser(
144
+ "score", help="score runs' traces", description=score.__doc__, formatter_class=fmt
145
+ )
146
+ score.add_arguments(p)
147
+ p.set_defaults(handler=_with_mlflow(score.main))
148
+
149
+ p = sub.add_parser("validate", help="check eval and harness files")
150
+ p.set_defaults(handler=validate)
151
+
152
+ p = sub.add_parser("mlflow", help="open the MLflow server (UI) in the foreground")
153
+ p.set_defaults(handler=serve)
154
+ return ap
155
+
156
+
157
+ def main() -> None:
158
+ """Parse the command line and run the chosen subcommand."""
159
+ args = build_parser().parse_args()
160
+ use_config(args.config)
161
+ code = args.handler(args)
162
+ sys.exit(code if isinstance(code, int) else 0)