repobench 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 (65) hide show
  1. repobench-0.1.0/.gitignore +1 -0
  2. repobench-0.1.0/Makefile +53 -0
  3. repobench-0.1.0/PKG-INFO +247 -0
  4. repobench-0.1.0/README.md +216 -0
  5. repobench-0.1.0/agentfit/__init__.py +3 -0
  6. repobench-0.1.0/agentfit/analysis/__init__.py +0 -0
  7. repobench-0.1.0/agentfit/analysis/metrics.py +58 -0
  8. repobench-0.1.0/agentfit/analysis/recommendation.py +104 -0
  9. repobench-0.1.0/agentfit/analysis/statistics.py +71 -0
  10. repobench-0.1.0/agentfit/benchmark/__init__.py +0 -0
  11. repobench-0.1.0/agentfit/benchmark/coverage.py +51 -0
  12. repobench-0.1.0/agentfit/benchmark/health.py +191 -0
  13. repobench-0.1.0/agentfit/benchmark/sampling.py +152 -0
  14. repobench-0.1.0/agentfit/cli/__init__.py +0 -0
  15. repobench-0.1.0/agentfit/cli/analyze.py +576 -0
  16. repobench-0.1.0/agentfit/cli/app.py +210 -0
  17. repobench-0.1.0/agentfit/cli/benchmark.py +295 -0
  18. repobench-0.1.0/agentfit/cli/candidates.py +119 -0
  19. repobench-0.1.0/agentfit/cli/config_cmd.py +106 -0
  20. repobench-0.1.0/agentfit/cli/doctor.py +240 -0
  21. repobench-0.1.0/agentfit/cli/init.py +284 -0
  22. repobench-0.1.0/agentfit/cli/report.py +224 -0
  23. repobench-0.1.0/agentfit/cli/run.py +358 -0
  24. repobench-0.1.0/agentfit/cli/task.py +169 -0
  25. repobench-0.1.0/agentfit/cli/telemetry.py +103 -0
  26. repobench-0.1.0/agentfit/cli/utils.py +46 -0
  27. repobench-0.1.0/agentfit/config.py +32 -0
  28. repobench-0.1.0/agentfit/harbor/__init__.py +0 -0
  29. repobench-0.1.0/agentfit/harbor/exporter.py +206 -0
  30. repobench-0.1.0/agentfit/harbor/parser.py +109 -0
  31. repobench-0.1.0/agentfit/harbor/runner.py +321 -0
  32. repobench-0.1.0/agentfit/logging.py +46 -0
  33. repobench-0.1.0/agentfit/mining/__init__.py +0 -0
  34. repobench-0.1.0/agentfit/mining/candidates.py +219 -0
  35. repobench-0.1.0/agentfit/models.py +375 -0
  36. repobench-0.1.0/agentfit/reporting/__init__.py +0 -0
  37. repobench-0.1.0/agentfit/reporting/json.py +61 -0
  38. repobench-0.1.0/agentfit/reporting/terminal.py +171 -0
  39. repobench-0.1.0/agentfit/repository/__init__.py +0 -0
  40. repobench-0.1.0/agentfit/repository/detection.py +424 -0
  41. repobench-0.1.0/agentfit/repository/git.py +309 -0
  42. repobench-0.1.0/agentfit/repository/github.py +261 -0
  43. repobench-0.1.0/agentfit/repository/workload.py +339 -0
  44. repobench-0.1.0/agentfit/storage/__init__.py +0 -0
  45. repobench-0.1.0/agentfit/storage/database.py +486 -0
  46. repobench-0.1.0/agentfit/storage/migrations/__init__.py +0 -0
  47. repobench-0.1.0/agentfit/tasks/__init__.py +0 -0
  48. repobench-0.1.0/agentfit/tasks/instruction.py +165 -0
  49. repobench-0.1.0/agentfit/tasks/leakage.py +139 -0
  50. repobench-0.1.0/agentfit/tasks/verifier.py +220 -0
  51. repobench-0.1.0/agentfit/utils.py +80 -0
  52. repobench-0.1.0/agentfit/validation/__init__.py +0 -0
  53. repobench-0.1.0/agentfit/validation/determinism.py +52 -0
  54. repobench-0.1.0/agentfit/validation/environment.py +160 -0
  55. repobench-0.1.0/agentfit/validation/noop.py +81 -0
  56. repobench-0.1.0/agentfit/validation/oracle.py +102 -0
  57. repobench-0.1.0/agentfit.yml +43 -0
  58. repobench-0.1.0/plans/go-java-support.md +121 -0
  59. repobench-0.1.0/plans/harbor-integration.md +188 -0
  60. repobench-0.1.0/pyproject.toml +64 -0
  61. repobench-0.1.0/tests/__init__.py +0 -0
  62. repobench-0.1.0/tests/test_benchmark_analysis.py +155 -0
  63. repobench-0.1.0/tests/test_instruction_mining.py +132 -0
  64. repobench-0.1.0/tests/test_workload_verifier.py +145 -0
  65. repobench-0.1.0/uv.lock +708 -0
@@ -0,0 +1 @@
1
+ .agentfit/
@@ -0,0 +1,53 @@
1
+ .PHONY: install install-dev build clean publish test lint
2
+
3
+ # Install for users
4
+ install:
5
+ pip install .
6
+
7
+ # Install for development
8
+ install-dev:
9
+ pip install -e ".[dev]"
10
+
11
+ # Install with pipx (recommended for CLI tools)
12
+ install-pipx:
13
+ pipx install .
14
+
15
+ # Build package
16
+ build: clean
17
+ python -m build
18
+
19
+ # Clean build artifacts
20
+ clean:
21
+ rm -rf dist/ build/ *.egg-info agentfit/*.pyc
22
+
23
+ # Publish to PyPI (test)
24
+ publish-test: build
25
+ twine upload --repository testpypi dist/*
26
+
27
+ # Publish to PyPI (production)
28
+ publish: build
29
+ twine upload dist/*
30
+
31
+ # Run tests
32
+ test:
33
+ pytest tests/ -v
34
+
35
+ # Run linter
36
+ lint:
37
+ ruff check agentfit/
38
+ ruff format agentfit/ --check
39
+
40
+ # Format code
41
+ format:
42
+ ruff check agentfit/ --fix
43
+ ruff format agentfit/
44
+
45
+ # Type check
46
+ typecheck:
47
+ mypy agentfit/
48
+
49
+ # Show package info
50
+ info:
51
+ @echo "Package: agentfit"
52
+ @echo "Version: $$(python -c 'from agentfit import __version__; print(__version__)')"
53
+ @echo "Entry point: agentfit = agentfit.cli.app:app"
@@ -0,0 +1,247 @@
1
+ Metadata-Version: 2.5
2
+ Name: repobench
3
+ Version: 0.1.0
4
+ Summary: Living repository-native evals for coding agents
5
+ Project-URL: Homepage, https://github.com/agentfit/agentfit
6
+ Project-URL: Repository, https://github.com/agentfit/agentfit
7
+ Project-URL: Issues, https://github.com/agentfit/agentfit/issues
8
+ Author: AgentFit Contributors
9
+ License-Expression: Apache-2.0
10
+ Keywords: benchmark,coding-agent,eval,harbor,swe-bench
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: Apache Software License
15
+ Classifier: Natural Language :: English
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Software Development :: Quality Assurance
19
+ Classifier: Topic :: Software Development :: Testing
20
+ Requires-Python: >=3.12
21
+ Requires-Dist: pydantic>=2.0.0
22
+ Requires-Dist: pyyaml>=6.0
23
+ Requires-Dist: rich>=13.0.0
24
+ Requires-Dist: typer>=0.12.0
25
+ Provides-Extra: dev
26
+ Requires-Dist: mypy; extra == 'dev'
27
+ Requires-Dist: pytest-cov; extra == 'dev'
28
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
29
+ Requires-Dist: ruff; extra == 'dev'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # AgentFit
33
+
34
+ **Living repository-native evals for coding agents.**
35
+
36
+ Find which coding agent actually works for your codebase.
37
+
38
+ ## Quick Start
39
+
40
+ ```bash
41
+ # Install
42
+ pipx install agentfit
43
+
44
+ # Initialize in your repo
45
+ cd my-project
46
+ agentfit init
47
+
48
+ # Analyze workload
49
+ agentfit analyze
50
+
51
+ # Build benchmark
52
+ agentfit benchmark build
53
+
54
+ # Run agents
55
+ agentfit run codex-local claude-local
56
+
57
+ # Get results
58
+ agentfit report
59
+ ```
60
+
61
+ ## What is AgentFit?
62
+
63
+ AgentFit transforms your repository's real engineering history into a private, reproducible, representative benchmark for coding agents.
64
+
65
+ Instead of relying on public benchmarks that don't match your work, AgentFit mines your actual merged PRs to create evals that reflect what your team actually ships.
66
+
67
+ ### The Pipeline
68
+
69
+ ```
70
+ Repository history
71
+
72
+
73
+ DISCOVER What happened here?
74
+
75
+
76
+ FILTER Can it become an eval?
77
+
78
+
79
+ VALIDATE Can we prove correctness?
80
+
81
+
82
+ REPRESENT Does the benchmark match
83
+ the actual workload?
84
+
85
+
86
+ RUN Execute agent configurations
87
+
88
+
89
+ MEASURE Quality / cost / latency
90
+
91
+
92
+ DECIDE What should we use?
93
+ ```
94
+
95
+ ## Commands
96
+
97
+ | Command | Description |
98
+ |---|---|
99
+ | `agentfit doctor` | Check prerequisites |
100
+ | `agentfit init` | Initialize in current repo |
101
+ | `agentfit analyze` | Analyze repository workload |
102
+ | `agentfit candidates` | View mined candidate tasks |
103
+ | `agentfit task inspect <id>` | Inspect a candidate task |
104
+ | `agentfit benchmark build` | Build representative benchmark |
105
+ | `agentfit benchmark list` | List existing benchmarks |
106
+ | `agentfit run <configs...>` | Run agents via Harbor |
107
+ | `agentfit report` | Generate comparison report |
108
+ | `agentfit config show` | Show current configuration |
109
+
110
+ ## Configuration
111
+
112
+ AgentFit creates `agentfit.yml` in your repo:
113
+
114
+ ```yaml
115
+ version: 1
116
+
117
+ repository:
118
+ provider: github
119
+ lookback_days: 180
120
+
121
+ project:
122
+ languages:
123
+ - python
124
+ - go
125
+ - java
126
+ install_command: pip install -e ".[dev]"
127
+ test_command: pytest
128
+
129
+ benchmark:
130
+ size: 24
131
+ dimensions:
132
+ task_type: 0.30
133
+ subsystem: 0.40
134
+ complexity: 0.30
135
+
136
+ execution:
137
+ environment: docker
138
+ concurrency: 4
139
+
140
+ agents:
141
+ codex-default:
142
+ agent: codex
143
+ model: openai/gpt-4o
144
+
145
+ claude-default:
146
+ agent: claude-code
147
+ model: anthropic/claude-opus-4
148
+ ```
149
+
150
+ ## Supported Languages
151
+
152
+ - Python
153
+ - JavaScript / TypeScript
154
+ - Go
155
+ - Java (Maven / Gradle)
156
+
157
+ ## How it Works
158
+
159
+ ### 1. Workload Analysis
160
+
161
+ AgentFit fetches your merged PRs via GitHub API and classifies each by:
162
+ - **Task type**: bugfix, feature, refactor
163
+ - **Subsystem**: payments, auth, frontend, etc.
164
+ - **Complexity**: small, medium, large
165
+
166
+ ### 2. Candidate Mining
167
+
168
+ From the workload, AgentFit identifies PRs that can become reliable evals:
169
+ - Have linked issues or clear PR descriptions
170
+ - Include test changes (verifier evidence)
171
+ - Are within configurable size bounds
172
+ - Don't require unsupported environments
173
+
174
+ ### 3. Validation Pipeline
175
+
176
+ Each candidate passes rigorous validation:
177
+ - **Base health**: tests pass before the change
178
+ - **No-op validation**: new tests fail without the fix
179
+ - **Oracle validation**: tests pass with the fix
180
+ - **Determinism**: tests produce consistent results
181
+ - **Leakage protection**: agent can't access gold solution
182
+
183
+ ### 4. Representative Sampling
184
+
185
+ AgentFit selects benchmark tasks using stratified optimization to match your actual workload distribution across task type, subsystem, and complexity.
186
+
187
+ ### 5. Execution via Harbor
188
+
189
+ Benchmarks are exported in Harbor format and executed via `harbor run`:
190
+
191
+ ```bash
192
+ agentfit run codex-default claude-default
193
+ ```
194
+
195
+ ### 6. Decision Report
196
+
197
+ ```bash
198
+ agentfit report
199
+ ```
200
+
201
+ ```
202
+ AGENTFIT REPORT
203
+ ────────────────────────────────────────
204
+
205
+ Repository: my-org/payments
206
+ Benchmark: af_b_20260825_a84f
207
+ Tasks: 24
208
+ Health: 87/100
209
+
210
+ Solve $/Solve
211
+ Codex 82% $0.71
212
+ Claude 86% $1.52
213
+
214
+ Quality
215
+ Claude +4pp vs Codex
216
+ 95% CI: -7pp → +14pp
217
+
218
+ No conclusive quality difference.
219
+
220
+ Recommendation: Codex
221
+ Reason: lowest cost among statistically
222
+ indistinguishable configurations.
223
+ ```
224
+
225
+ ## Development
226
+
227
+ ```bash
228
+ # Clone
229
+ git clone https://github.com/agentfit/agentfit.git
230
+ cd agentfit
231
+
232
+ # Install dev dependencies
233
+ pip install -e ".[dev]"
234
+
235
+ # Run tests
236
+ pytest
237
+
238
+ # Build package
239
+ python -m build
240
+
241
+ # Publish to PyPI
242
+ twine upload dist/*
243
+ ```
244
+
245
+ ## License
246
+
247
+ Apache 2.0
@@ -0,0 +1,216 @@
1
+ # AgentFit
2
+
3
+ **Living repository-native evals for coding agents.**
4
+
5
+ Find which coding agent actually works for your codebase.
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ # Install
11
+ pipx install agentfit
12
+
13
+ # Initialize in your repo
14
+ cd my-project
15
+ agentfit init
16
+
17
+ # Analyze workload
18
+ agentfit analyze
19
+
20
+ # Build benchmark
21
+ agentfit benchmark build
22
+
23
+ # Run agents
24
+ agentfit run codex-local claude-local
25
+
26
+ # Get results
27
+ agentfit report
28
+ ```
29
+
30
+ ## What is AgentFit?
31
+
32
+ AgentFit transforms your repository's real engineering history into a private, reproducible, representative benchmark for coding agents.
33
+
34
+ Instead of relying on public benchmarks that don't match your work, AgentFit mines your actual merged PRs to create evals that reflect what your team actually ships.
35
+
36
+ ### The Pipeline
37
+
38
+ ```
39
+ Repository history
40
+
41
+
42
+ DISCOVER What happened here?
43
+
44
+
45
+ FILTER Can it become an eval?
46
+
47
+
48
+ VALIDATE Can we prove correctness?
49
+
50
+
51
+ REPRESENT Does the benchmark match
52
+ the actual workload?
53
+
54
+
55
+ RUN Execute agent configurations
56
+
57
+
58
+ MEASURE Quality / cost / latency
59
+
60
+
61
+ DECIDE What should we use?
62
+ ```
63
+
64
+ ## Commands
65
+
66
+ | Command | Description |
67
+ |---|---|
68
+ | `agentfit doctor` | Check prerequisites |
69
+ | `agentfit init` | Initialize in current repo |
70
+ | `agentfit analyze` | Analyze repository workload |
71
+ | `agentfit candidates` | View mined candidate tasks |
72
+ | `agentfit task inspect <id>` | Inspect a candidate task |
73
+ | `agentfit benchmark build` | Build representative benchmark |
74
+ | `agentfit benchmark list` | List existing benchmarks |
75
+ | `agentfit run <configs...>` | Run agents via Harbor |
76
+ | `agentfit report` | Generate comparison report |
77
+ | `agentfit config show` | Show current configuration |
78
+
79
+ ## Configuration
80
+
81
+ AgentFit creates `agentfit.yml` in your repo:
82
+
83
+ ```yaml
84
+ version: 1
85
+
86
+ repository:
87
+ provider: github
88
+ lookback_days: 180
89
+
90
+ project:
91
+ languages:
92
+ - python
93
+ - go
94
+ - java
95
+ install_command: pip install -e ".[dev]"
96
+ test_command: pytest
97
+
98
+ benchmark:
99
+ size: 24
100
+ dimensions:
101
+ task_type: 0.30
102
+ subsystem: 0.40
103
+ complexity: 0.30
104
+
105
+ execution:
106
+ environment: docker
107
+ concurrency: 4
108
+
109
+ agents:
110
+ codex-default:
111
+ agent: codex
112
+ model: openai/gpt-4o
113
+
114
+ claude-default:
115
+ agent: claude-code
116
+ model: anthropic/claude-opus-4
117
+ ```
118
+
119
+ ## Supported Languages
120
+
121
+ - Python
122
+ - JavaScript / TypeScript
123
+ - Go
124
+ - Java (Maven / Gradle)
125
+
126
+ ## How it Works
127
+
128
+ ### 1. Workload Analysis
129
+
130
+ AgentFit fetches your merged PRs via GitHub API and classifies each by:
131
+ - **Task type**: bugfix, feature, refactor
132
+ - **Subsystem**: payments, auth, frontend, etc.
133
+ - **Complexity**: small, medium, large
134
+
135
+ ### 2. Candidate Mining
136
+
137
+ From the workload, AgentFit identifies PRs that can become reliable evals:
138
+ - Have linked issues or clear PR descriptions
139
+ - Include test changes (verifier evidence)
140
+ - Are within configurable size bounds
141
+ - Don't require unsupported environments
142
+
143
+ ### 3. Validation Pipeline
144
+
145
+ Each candidate passes rigorous validation:
146
+ - **Base health**: tests pass before the change
147
+ - **No-op validation**: new tests fail without the fix
148
+ - **Oracle validation**: tests pass with the fix
149
+ - **Determinism**: tests produce consistent results
150
+ - **Leakage protection**: agent can't access gold solution
151
+
152
+ ### 4. Representative Sampling
153
+
154
+ AgentFit selects benchmark tasks using stratified optimization to match your actual workload distribution across task type, subsystem, and complexity.
155
+
156
+ ### 5. Execution via Harbor
157
+
158
+ Benchmarks are exported in Harbor format and executed via `harbor run`:
159
+
160
+ ```bash
161
+ agentfit run codex-default claude-default
162
+ ```
163
+
164
+ ### 6. Decision Report
165
+
166
+ ```bash
167
+ agentfit report
168
+ ```
169
+
170
+ ```
171
+ AGENTFIT REPORT
172
+ ────────────────────────────────────────
173
+
174
+ Repository: my-org/payments
175
+ Benchmark: af_b_20260825_a84f
176
+ Tasks: 24
177
+ Health: 87/100
178
+
179
+ Solve $/Solve
180
+ Codex 82% $0.71
181
+ Claude 86% $1.52
182
+
183
+ Quality
184
+ Claude +4pp vs Codex
185
+ 95% CI: -7pp → +14pp
186
+
187
+ No conclusive quality difference.
188
+
189
+ Recommendation: Codex
190
+ Reason: lowest cost among statistically
191
+ indistinguishable configurations.
192
+ ```
193
+
194
+ ## Development
195
+
196
+ ```bash
197
+ # Clone
198
+ git clone https://github.com/agentfit/agentfit.git
199
+ cd agentfit
200
+
201
+ # Install dev dependencies
202
+ pip install -e ".[dev]"
203
+
204
+ # Run tests
205
+ pytest
206
+
207
+ # Build package
208
+ python -m build
209
+
210
+ # Publish to PyPI
211
+ twine upload dist/*
212
+ ```
213
+
214
+ ## License
215
+
216
+ Apache 2.0
@@ -0,0 +1,3 @@
1
+ """AgentFit — Living repository-native evals for coding agents."""
2
+
3
+ __version__ = "0.1.0"
File without changes
@@ -0,0 +1,58 @@
1
+ """Configuration metrics aggregation from trials."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import statistics
6
+ from typing import Iterable
7
+
8
+ from agentfit.logging import get_logger
9
+ from agentfit.models import ConfigMetrics, Trial
10
+ from agentfit.analysis.statistics import wilson_ci
11
+
12
+ log = get_logger("analysis.metrics")
13
+
14
+
15
+ def compute_config_metrics(trials: Iterable[Trial]) -> ConfigMetrics:
16
+ """Compute aggregated metrics for one agent configuration."""
17
+ trials = list(trials)
18
+ metrics = ConfigMetrics()
19
+
20
+ if not trials:
21
+ return metrics
22
+
23
+ solved = [t for t in trials if t.solved]
24
+ metrics.solved = len(solved)
25
+ metrics.total = len(trials)
26
+ metrics.pass_rate = len(solved) / len(trials)
27
+
28
+ # Wilson 95% CI
29
+ ci_lower, ci_upper = wilson_ci(len(solved), len(trials))
30
+ metrics.ci_lower = ci_lower
31
+ metrics.ci_upper = ci_upper
32
+
33
+ # Economics
34
+ costs = [t.cost_usd for t in trials if t.cost_usd is not None]
35
+ if costs:
36
+ metrics.total_cost = sum(costs)
37
+ metrics.mean_cost_task = metrics.total_cost / len(costs)
38
+ if metrics.solved > 0:
39
+ metrics.cost_per_solve = metrics.total_cost / metrics.solved
40
+
41
+ # Efficiency
42
+ prompt_tokens = [t.prompt_tokens for t in trials if t.prompt_tokens is not None]
43
+ completion_tokens = [t.completion_tokens for t in trials if t.completion_tokens is not None]
44
+ metrics.total_prompt_tokens = sum(prompt_tokens)
45
+ metrics.total_completion_tokens = sum(completion_tokens)
46
+ if metrics.solved > 0:
47
+ total_tokens = metrics.total_prompt_tokens + metrics.total_completion_tokens
48
+ metrics.tokens_per_solve = round(total_tokens / metrics.solved)
49
+
50
+ # Performance
51
+ durations = [t.duration_ms for t in trials if t.duration_ms is not None]
52
+ if durations:
53
+ metrics.p50_duration_ms = round(statistics.median(durations))
54
+ sorted_d = sorted(durations)
55
+ p90_idx = min(len(sorted_d) - 1, round(0.9 * (len(sorted_d) - 1)))
56
+ metrics.p90_duration_ms = sorted_d[p90_idx]
57
+
58
+ return metrics
@@ -0,0 +1,104 @@
1
+ """Cost-aware recommendation logic."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Iterable
6
+
7
+ from agentfit.logging import get_logger
8
+ from agentfit.models import ConfigMetrics
9
+
10
+ log = get_logger("analysis.recommendation")
11
+
12
+
13
+ def recommend(
14
+ metrics_dict: dict[str, ConfigMetrics],
15
+ ) -> tuple[str | None, str]:
16
+ """Recommend a default agent configuration.
17
+
18
+ Policy (``cost_effective``):
19
+ 1. Find the configuration with the highest observed pass rate.
20
+ 2. Identify configurations whose quality difference is not
21
+ statistically conclusive (overlapping Wilson CIs).
22
+ 3. Within that set, choose the lowest cost per verified solve.
23
+
24
+ Returns (config_name, reason).
25
+ """
26
+ if not metrics_dict:
27
+ return None, "No configurations with results available."
28
+
29
+ # Consider only configs with trials
30
+ with_results = {name: m for name, m in metrics_dict.items() if m.total > 0}
31
+ if not with_results:
32
+ return None, "No configurations have completed trials."
33
+
34
+ # 1. Best observed pass rate
35
+ best_name = max(with_results, key=lambda n: with_results[n].pass_rate)
36
+ best = with_results[best_name]
37
+
38
+ # 2. Statistically indistinguishable set (overlapping Wilson CIs)
39
+ indistinguishable = []
40
+ for name, m in with_results.items():
41
+ if _cis_overlap(best, m):
42
+ indistinguishable.append(name)
43
+
44
+ # 3. Lowest cost per verified solve among indistinguishable
45
+ with_cost = [
46
+ (name, with_results[name])
47
+ for name in indistinguishable
48
+ if with_results[name].cost_per_solve is not None
49
+ ]
50
+
51
+ if with_cost:
52
+ recommended = min(with_cost, key=lambda nm: nm[1].cost_per_solve)[0]
53
+ reason = (
54
+ f"lowest cost per verified solve among configurations "
55
+ f"statistically indistinguishable from observed best quality."
56
+ )
57
+ else:
58
+ # No cost data: pick best observed pass rate
59
+ recommended = best_name
60
+ reason = "best observed pass rate (no cost data available)."
61
+
62
+ log.info("Recommendation: %s (%s)", recommended, reason)
63
+ return recommended, reason
64
+
65
+
66
+ def pareto_frontier(
67
+ metrics_dict: dict[str, ConfigMetrics],
68
+ ) -> list[str]:
69
+ """Identify configurations on the Pareto frontier.
70
+
71
+ A configuration is dominated if another config has >= pass rate AND
72
+ <= cost per solve (with at least one strict inequality).
73
+
74
+ Returns config names on the frontier (highest quality first).
75
+ """
76
+ with_cost = {
77
+ name: m for name, m in metrics_dict.items()
78
+ if m.total > 0 and m.cost_per_solve is not None
79
+ }
80
+ if not with_cost:
81
+ return list(metrics_dict.keys())
82
+
83
+ frontier: list[str] = []
84
+ for name, m in with_cost.items():
85
+ dominated = False
86
+ for other_name, other in with_cost.items():
87
+ if other_name == name:
88
+ continue
89
+ if (other.pass_rate >= m.pass_rate
90
+ and other.cost_per_solve <= m.cost_per_solve
91
+ and (other.pass_rate > m.pass_rate
92
+ or other.cost_per_solve < m.cost_per_solve)):
93
+ dominated = True
94
+ break
95
+ if not dominated:
96
+ frontier.append(name)
97
+
98
+ frontier.sort(key=lambda n: with_cost[n].pass_rate, reverse=True)
99
+ return frontier
100
+
101
+
102
+ def _cis_overlap(a: ConfigMetrics, b: ConfigMetrics) -> bool:
103
+ """Check if two Wilson CIs overlap (statistically indistinguishable)."""
104
+ return not (a.ci_upper < b.ci_lower or b.ci_upper < a.ci_lower)