pluginbench 0.5.0__py3-none-any.whl

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 (58) hide show
  1. pluginbench/__init__.py +3 -0
  2. pluginbench/__main__.py +3 -0
  3. pluginbench/assessment-charts.html +790 -0
  4. pluginbench/assessment.py +316 -0
  5. pluginbench/assessment_charts.py +1225 -0
  6. pluginbench/assessment_metrics.py +1956 -0
  7. pluginbench/assessment_models.py +127 -0
  8. pluginbench/baselines.py +792 -0
  9. pluginbench/claude.py +374 -0
  10. pluginbench/cli.py +672 -0
  11. pluginbench/code_diff.py +207 -0
  12. pluginbench/codex.py +451 -0
  13. pluginbench/codex_events.py +585 -0
  14. pluginbench/collectors.py +591 -0
  15. pluginbench/compare.py +927 -0
  16. pluginbench/components.py +129 -0
  17. pluginbench/coverage_report.py +610 -0
  18. pluginbench/datasets.py +207 -0
  19. pluginbench/evaluation.example.yaml +45 -0
  20. pluginbench/evaluation.py +1651 -0
  21. pluginbench/evaluation_report.py +1004 -0
  22. pluginbench/fallow_parse.mjs +101 -0
  23. pluginbench/harbor_backend.py +2946 -0
  24. pluginbench/isolation.py +314 -0
  25. pluginbench/judge.py +683 -0
  26. pluginbench/language_analysis.py +915 -0
  27. pluginbench/ledger.py +217 -0
  28. pluginbench/measurement_profiles.py +616 -0
  29. pluginbench/native_treatment_runtime.js +221 -0
  30. pluginbench/native_treatments.py +155 -0
  31. pluginbench/observer_data/go-analysis-v1/go.mod +10 -0
  32. pluginbench/observer_data/go-analysis-v1/go.sum +10 -0
  33. pluginbench/observer_data/go-analysis-v1/main.go +64 -0
  34. pluginbench/observer_data/oxc-parser-0.126.0/package-lock.json +449 -0
  35. pluginbench/observer_data/oxc-parser-0.126.0/package.json +5 -0
  36. pluginbench/parallel.py +101 -0
  37. pluginbench/processes.py +100 -0
  38. pluginbench/remeasure.py +992 -0
  39. pluginbench/run_lock.py +227 -0
  40. pluginbench/run_status.py +518 -0
  41. pluginbench/study.py +1858 -0
  42. pluginbench/study_review.py +924 -0
  43. pluginbench/submissions.py +486 -0
  44. pluginbench/task_sources.py +403 -0
  45. pluginbench/treatments.py +388 -0
  46. pluginbench/trial_containers.py +94 -0
  47. pluginbench/vendor/ECHARTS-LICENSE +222 -0
  48. pluginbench/vendor/ECHARTS-NOTICE +5 -0
  49. pluginbench/vendor/ZRENDER-LICENSE +29 -0
  50. pluginbench/vendor/echarts.min.js +48 -0
  51. pluginbench-0.5.0.dist-info/METADATA +294 -0
  52. pluginbench-0.5.0.dist-info/RECORD +58 -0
  53. pluginbench-0.5.0.dist-info/WHEEL +4 -0
  54. pluginbench-0.5.0.dist-info/entry_points.txt +2 -0
  55. pluginbench-0.5.0.dist-info/licenses/LICENSE +201 -0
  56. pluginbench-0.5.0.dist-info/licenses/src/pluginbench/vendor/ECHARTS-LICENSE +222 -0
  57. pluginbench-0.5.0.dist-info/licenses/src/pluginbench/vendor/ECHARTS-NOTICE +5 -0
  58. pluginbench-0.5.0.dist-info/licenses/src/pluginbench/vendor/ZRENDER-LICENSE +29 -0
@@ -0,0 +1,207 @@
1
+ """Frozen source and standard Harbor task contracts for evaluation studies."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+ from typing import Literal
10
+
11
+ import yaml
12
+ from pydantic import BaseModel, ConfigDict, Field
13
+
14
+ from pluginbench import harbor_backend, submissions
15
+ from pluginbench.assessment_metrics import inspect_snapshot
16
+
17
+
18
+ class CatalogModel(BaseModel):
19
+ model_config = ConfigDict(extra="forbid")
20
+
21
+
22
+ class TaskEntry(CatalogModel):
23
+ id: str = Field(min_length=1)
24
+ prompt: str = Field(min_length=1)
25
+ fixture: Path
26
+ native_task: Path
27
+ provenance: dict[str, str]
28
+
29
+
30
+ class TaskCatalog(CatalogModel):
31
+ schema_version: Literal[1]
32
+ tasks: list[TaskEntry] = Field(min_length=1)
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class TaskSpec:
37
+ task_id: str
38
+ prompt: str
39
+ fixture: Path
40
+ native_task: Path
41
+ digest: str
42
+ provenance: dict[str, str]
43
+
44
+
45
+ def native_identity(task: TaskSpec | TaskEntry) -> dict[str, object]:
46
+ """Read the prepared runtime payload without hiding incomplete preparation."""
47
+ value = json.loads(task.provenance.get("native_task_identity", "null"))
48
+ if not isinstance(value, dict) or not all(isinstance(key, str) for key in value):
49
+ raise ValueError("task requires a prepared native_task_identity")
50
+ cleanup = value.get("cleanup")
51
+ if (
52
+ value.get("status") != "complete"
53
+ or not isinstance(cleanup, dict)
54
+ or cleanup.get("status") != "complete"
55
+ ):
56
+ raise ValueError("native task preparation or cleanup is incomplete")
57
+ return value
58
+
59
+
60
+ def portable_native_identity(identity: dict[str, object]) -> dict[str, object]:
61
+ """Run-folder locations are not part of a reusable coding identity."""
62
+ keys = {
63
+ "task_assets_sha256",
64
+ "original_task_assets_sha256",
65
+ "source_digest",
66
+ "workdir",
67
+ "image_identity",
68
+ "toolchain_identity",
69
+ "grader_image_identity",
70
+ "resources",
71
+ "isolation",
72
+ "trusted_tests_sha256",
73
+ "verifier_test_delivery",
74
+ "transport_policy",
75
+ "preparation_key",
76
+ "preparation_policy",
77
+ }
78
+ missing = keys - identity.keys()
79
+ if missing:
80
+ raise ValueError("native coding identity is incomplete: " + ", ".join(sorted(missing)))
81
+ portable = {
82
+ **{key: identity[key] for key in sorted(keys)},
83
+ "source_policy": identity.get("source_policy", "legacy-unqualified"),
84
+ "source_qualification": submissions.portable_qualification(
85
+ identity.get("source_qualification")
86
+ ),
87
+ }
88
+ # Absent when the task sets its own git identity, so identities saved before it still match.
89
+ if "supplied_git_identity" in identity:
90
+ portable["supplied_git_identity"] = identity["supplied_git_identity"]
91
+ return portable
92
+
93
+
94
+ def _catalog_path(source: Path, item: Path) -> Path:
95
+ if item.is_absolute() or ".." in item.parts:
96
+ raise ValueError("task source and native assets must be relative to their catalog")
97
+ raw = source.parent / item
98
+ if any(candidate.is_symlink() for candidate in (raw, *raw.parents)):
99
+ raise ValueError(f"task input must not be a symlink: {raw}")
100
+ resolved = raw.resolve()
101
+ if not resolved.is_dir():
102
+ raise ValueError(f"task input is not a directory: {resolved}")
103
+ return resolved
104
+
105
+
106
+ def source_metadata(source: Path) -> dict[str, dict[str, object]]:
107
+ return {
108
+ path.relative_to(source).as_posix(): {
109
+ "mode": path.stat().st_mode & 0o111,
110
+ "directory": path.is_dir(),
111
+ }
112
+ for path in sorted(source.rglob("*"))
113
+ }
114
+
115
+
116
+ def _task_digest(entry: TaskEntry, fixture: Path, native_task: Path) -> str:
117
+ identity = native_identity(entry)
118
+ source_digest = inspect_snapshot(fixture)["digest"]
119
+ asset_digest = harbor_backend.native_task_assets(native_task)
120
+ if source_digest != identity.get("source_digest"):
121
+ raise ValueError("prepared native source changed")
122
+ if asset_digest != identity.get("task_assets_sha256"):
123
+ raise ValueError("prepared native task assets changed")
124
+ return _contract_digest(
125
+ task_id=entry.id,
126
+ prompt=entry.prompt,
127
+ provenance=entry.provenance,
128
+ fixture=fixture,
129
+ source_digest=source_digest,
130
+ native=portable_native_identity(identity),
131
+ )
132
+
133
+
134
+ def task_digest(task: TaskSpec, native: dict[str, object]) -> str:
135
+ """The task's digest as it would be with `native` as its portable native identity.
136
+
137
+ A saved vanilla cohort's digest covers its own native identity, so it is checked against
138
+ the cohort's saved identity, not this run's.
139
+ """
140
+ return _contract_digest(
141
+ task_id=task.task_id,
142
+ prompt=task.prompt,
143
+ provenance=task.provenance,
144
+ fixture=task.fixture,
145
+ source_digest=inspect_snapshot(task.fixture)["digest"],
146
+ native=native,
147
+ )
148
+
149
+
150
+ def _contract_digest(
151
+ *,
152
+ task_id: str,
153
+ prompt: str,
154
+ provenance: dict[str, str],
155
+ fixture: Path,
156
+ source_digest: object,
157
+ native: dict[str, object],
158
+ ) -> str:
159
+ contract = {
160
+ "id": task_id,
161
+ "prompt": prompt,
162
+ "provenance": {
163
+ key: value for key, value in provenance.items() if key != "native_task_identity"
164
+ },
165
+ "source_digest": source_digest,
166
+ "source_metadata": source_metadata(fixture),
167
+ "native_task_identity": native,
168
+ }
169
+ encoded = json.dumps(contract, sort_keys=True, separators=(",", ":")).encode()
170
+ return "sha256:" + hashlib.sha256(encoded).hexdigest()
171
+
172
+
173
+ def load_tasks(source: Path, task_ids: list[str] | None = None) -> list[TaskSpec]:
174
+ try:
175
+ loaded = yaml.safe_load(source.read_text())
176
+ except (OSError, yaml.YAMLError) as exc:
177
+ raise ValueError(f"cannot read task catalog {source}: {exc}") from exc
178
+ catalog = TaskCatalog.model_validate(loaded)
179
+ by_id = {entry.id: entry for entry in catalog.tasks}
180
+ if len(by_id) != len(catalog.tasks):
181
+ raise ValueError("task catalog contains duplicate task IDs")
182
+ selected = list(by_id) if task_ids is None else task_ids
183
+ if not selected or any(not task_id.strip() for task_id in selected):
184
+ raise ValueError("selected task IDs must be nonempty")
185
+ if len(set(selected)) != len(selected):
186
+ raise ValueError("selected task IDs must be unique")
187
+ missing = [task_id for task_id in selected if task_id not in by_id]
188
+ if missing:
189
+ raise ValueError("selected task IDs are missing from catalog: " + ", ".join(missing))
190
+ tasks = []
191
+ for task_id in selected:
192
+ entry = by_id[task_id]
193
+ fixture = _catalog_path(source, entry.fixture)
194
+ native_task = _catalog_path(source, entry.native_task)
195
+ if native_task.is_relative_to(fixture) or fixture.is_relative_to(native_task):
196
+ raise ValueError("native task assets must be outside the writable source fixture")
197
+ tasks.append(
198
+ TaskSpec(
199
+ task_id,
200
+ entry.prompt,
201
+ fixture,
202
+ native_task,
203
+ _task_digest(entry, fixture, native_task),
204
+ entry.provenance,
205
+ )
206
+ )
207
+ return tasks
@@ -0,0 +1,45 @@
1
+ name: Workflow versus Superpowers
2
+ runner:
3
+ harness: codex # Native treatment coding currently requires Codex.
4
+ model: gpt-6.1-sol
5
+ reasoning: medium
6
+
7
+ treatments:
8
+ workflow:
9
+ plugins: [/path/to/workflow-plugin]
10
+ superpowers:
11
+ skills: [/path/to/superpowers/skills]
12
+ # Optional guidance: [/path/to/core.md] uses native global instructions.
13
+ # Vanilla is included automatically; every arm keeps the same task prompt.
14
+ capabilities:
15
+ subagents: true
16
+ max_threads: 4 # Concurrent children, not a lifetime call/spend cap.
17
+
18
+ tasks:
19
+ dataset: datacurve/deep-swe-1-1
20
+ version: sha256:5affcd534fd90ac85d202d4c63f8b35ddc942140afdd5d60014a21365440a2f5
21
+ ids:
22
+ - ofetch-per-origin-circuit-breaker
23
+ - testem-per-launcher-reports
24
+ - happy-dom-abort-pending-body-reads
25
+
26
+ measurements: [tokens, lines, cyclomatic, cognitive, dead_code, review]
27
+ # Unsupported measurements remain unknown with a reason.
28
+ # Add coverage after configuring a command and supported coverage output supported by every selected task image:
29
+ # measurement_settings:
30
+ # coverage:
31
+ # command: [npm, run, coverage]
32
+ # report_path: coverage/lcov.info
33
+ # tool_version: "the exact installed coverage tool version"
34
+ reviewer:
35
+ harness: codex
36
+ model: gpt-6-astra
37
+ reasoning: high
38
+ guidance: [/path/to/fixed-review-standards.md] # Same criteria for all outputs.
39
+
40
+ repeats: 3
41
+ # jobs: 2 # Optional, 1 to 4 (default 2); fails if it exceeds Docker capacity.
42
+ # CPU and memory default to task.toml; Docker must fit them before any model call.
43
+ # container_cpus: 2
44
+ # container_memory_mb: 8192
45
+ # timeout_seconds: 1800 # Separate agent budget; shorter than these tasks declare.